diff --git a/src/lib/utils/asciidoc_metadata.ts b/src/lib/utils/asciidoc_metadata.ts index 8367469..810c965 100644 --- a/src/lib/utils/asciidoc_metadata.ts +++ b/src/lib/utils/asciidoc_metadata.ts @@ -115,6 +115,13 @@ function mapAttributesToMetadata( } else if (metadataKey === "tags") { // Skip tags mapping since it's handled by extractTagsFromAttributes continue; + } else if (metadataKey === "summary") { + // Handle summary specially - combine with existing summary if present + if (metadata.summary) { + metadata.summary = `${metadata.summary} ${value}`; + } else { + metadata.summary = value; + } } else { (metadata as any)[metadataKey] = value; } @@ -123,84 +130,178 @@ function mapAttributesToMetadata( } /** - * Extracts authors from header line (document or section) + * Extracts authors from document header only (not sections) */ -function extractAuthorsFromHeader( - sourceContent: string, - isSection: boolean = false, -): string[] { +function extractDocumentAuthors(sourceContent: string): string[] { const authors: string[] = []; const lines = sourceContent.split(/\r?\n/); - const headerPattern = isSection ? /^==\s+/ : /^=\s+/; - + + // Find the document title line + let titleLineIndex = -1; for (let i = 0; i < lines.length; i++) { + if (lines[i].match(/^=\s+/)) { + titleLineIndex = i; + break; + } + } + + if (titleLineIndex === -1) { + return authors; + } + + // Look for authors in the lines immediately following the title + let i = titleLineIndex + 1; + while (i < lines.length) { const line = lines[i]; - if (line.match(headerPattern)) { - // Found title line, check subsequent lines for authors - let j = i + 1; - while (j < lines.length) { - const authorLine = lines[j]; - - // Stop if we hit a blank line or content that's not an author - if (authorLine.trim() === "") { - break; - } - - if (authorLine.includes("<") && !authorLine.startsWith(":")) { - // This is an author line like "John Doe " - const authorName = authorLine.split("<")[0].trim(); - if (authorName) { - authors.push(authorName); - } - } else if ( - isSection && authorLine.match(/^[A-Za-z\s]+$/) && - authorLine.trim() !== "" && authorLine.trim().split(/\s+/).length <= 2 - ) { - // This is a simple author name without email (for sections) - authors.push(authorLine.trim()); - } else if (authorLine.startsWith(":")) { - // This is an attribute line, skip it - attributes are handled by mapAttributesToMetadata - // Don't break here, continue to next line - } else { - // Not an author line, stop looking - break; - } - - j++; + + // Stop if we hit a blank line, section header, or content that's not an author + if (line.trim() === "" || line.match(/^==\s+/)) { + break; + } + + if (line.includes("<") && !line.startsWith(":")) { + // This is an author line like "John Doe " + const authorName = line.split("<")[0].trim(); + if (authorName) { + authors.push(authorName); } + } else if (line.startsWith(":")) { + // This is an attribute line, skip it + // Don't break here, continue to next line + } else { + // Not an author line, stop looking break; } + + i++; } + + return authors; +} +/** + * Extracts authors from section header only + */ +function extractSectionAuthors(sectionContent: string): string[] { + const authors: string[] = []; + const lines = sectionContent.split(/\r?\n/); + + // Find the section title line + let titleLineIndex = -1; + for (let i = 0; i < lines.length; i++) { + if (lines[i].match(/^==\s+/)) { + titleLineIndex = i; + break; + } + } + + if (titleLineIndex === -1) { + return authors; + } + + // Look for authors in the lines immediately following the section title + let i = titleLineIndex + 1; + while (i < lines.length) { + const line = lines[i]; + + // Stop if we hit a blank line, another section header, or content that's not an author + if (line.trim() === "" || line.match(/^==\s+/)) { + break; + } + + if (line.includes("<") && !line.startsWith(":")) { + // This is an author line like "John Doe " + const authorName = line.split("<")[0].trim(); + if (authorName) { + authors.push(authorName); + } + } else if ( + line.match(/^[A-Za-z\s]+$/) && + line.trim() !== "" && + line.trim().split(/\s+/).length <= 2 && + !line.startsWith(":") + ) { + // This is a simple author name without email (for sections) + authors.push(line.trim()); + } else if (line.startsWith(":")) { + // This is an attribute line, skip it + // Don't break here, continue to next line + } else { + // Not an author line, stop looking + break; + } + + i++; + } + return authors; } /** - * Strips header and attribute lines from content + * Strips document header and attribute lines from content */ -function stripHeaderAndAttributes( - content: string, - isSection: boolean = false, -): string { +function stripDocumentHeader(content: string): string { const lines = content.split(/\r?\n/); let contentStart = 0; - const headerPattern = isSection ? /^==\s+/ : /^=\s+/; - + + // Find where the document header ends for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Skip title line, author line, revision line, and attribute lines if ( - !line.match(headerPattern) && !line.includes("<") && + !line.match(/^=\s+/) && + !line.includes("<") && !line.match(/^.+,\s*.+:\s*.+$/) && - !line.match(/^:[^:]+:\s*.+$/) && line.trim() !== "" + !line.match(/^:[^:]+:\s*.+$/) && + line.trim() !== "" ) { contentStart = i; break; } } - + // Filter out all attribute lines and author lines from the content const contentLines = lines.slice(contentStart); + const filteredLines = contentLines.filter((line) => { + // Skip attribute lines + if (line.match(/^:[^:]+:\s*.+$/)) { + return false; + } + return true; + }); + + // Remove extra blank lines and normalize newlines + return filteredLines.join("\n").replace(/\n\s*\n\s*\n/g, "\n\n").replace( + /\n\s*\n/g, + "\n", + ).trim(); +} + +/** + * Strips section header and attribute lines from content + */ +function stripSectionHeader(sectionContent: string): string { + const lines = sectionContent.split(/\r?\n/); + let contentStart = 0; + + // Find where the section header ends + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Skip section title line, author line, and attribute lines + if ( + !line.match(/^==\s+/) && + !line.includes("<") && + !line.match(/^:[^:]+:\s*.+$/) && + line.trim() !== "" && + !(line.match(/^[A-Za-z\s]+$/) && line.trim() !== "" && line.trim().split(/\s+/).length <= 2) + ) { + contentStart = i; + break; + } + } + + // Filter out all attribute lines, author lines, and section headers from the content + const contentLines = lines.slice(contentStart); const filteredLines = contentLines.filter((line) => { // Skip attribute lines if (line.match(/^:[^:]+:\s*.+$/)) { @@ -208,14 +309,19 @@ function stripHeaderAndAttributes( } // Skip author lines (simple names without email) if ( - isSection && line.match(/^[A-Za-z\s]+$/) && line.trim() !== "" && + line.match(/^[A-Za-z\s]+$/) && + line.trim() !== "" && line.trim().split(/\s+/).length <= 2 ) { return false; } + // Skip section headers + if (line.match(/^==\s+/)) { + return false; + } return true; }); - + // Remove extra blank lines and normalize newlines return filteredLines.join("\n").replace(/\n\s*\n\s*\n/g, "\n\n").replace( /\n\s*\n/g, @@ -258,18 +364,40 @@ export function extractDocumentMetadata(inputContent: string): { // Extract basic metadata const title = document.getTitle(); - if (title) metadata.title = title; + if (title) { + // Decode HTML entities in the title + metadata.title = title + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, " "); + } // Handle multiple authors - combine header line and attributes - const authors = extractAuthorsFromHeader(document.getSource()); - - // Get authors from attributes (but avoid duplicates) - const attrAuthor = attributes["author"]; - if ( - attrAuthor && typeof attrAuthor === "string" && - !authors.includes(attrAuthor) - ) { - authors.push(attrAuthor); + const authors = extractDocumentAuthors(document.getSource()); + + // Get authors from attributes in the document header only (including multiple :author: lines) + const lines = document.getSource().split(/\r?\n/); + let inDocumentHeader = true; + for (const line of lines) { + // Stop scanning when we hit a section header + if (line.match(/^==\s+/)) { + inDocumentHeader = false; + break; + } + + // Process :author: attributes regardless of other content + if (inDocumentHeader) { + const match = line.match(/^:author:\s*(.+)$/); + if (match) { + const authorName = match[1].trim(); + if (authorName && !authors.includes(authorName)) { + authors.push(authorName); + } + } + } } if (authors.length > 0) { @@ -305,7 +433,7 @@ export function extractDocumentMetadata(inputContent: string): { metadata.tags = tags; } - const content = stripHeaderAndAttributes(document.getSource()); + const content = stripDocumentHeader(document.getSource()); return { metadata, content }; } @@ -335,13 +463,28 @@ export function extractSectionMetadata(inputSectionContent: string): { const attributes = parseSectionAttributes(inputSectionContent); // Extract authors from section content - const authors = extractAuthorsFromHeader(inputSectionContent, true); + const authors = extractSectionAuthors(inputSectionContent); + + // Get authors from attributes (including multiple :author: lines) + const lines = inputSectionContent.split(/\r?\n/); + for (const line of lines) { + const match = line.match(/^:author:\s*(.+)$/); + if (match) { + const authorName = match[1].trim(); + if (authorName && !authors.includes(authorName)) { + authors.push(authorName); + } + } + } + if (authors.length > 0) { metadata.authors = authors; } - // Map attributes to metadata (sections can have authors) - mapAttributesToMetadata(attributes, metadata, false); + // Map attributes to metadata (sections can have authors, but skip author mapping to avoid duplication) + const attributesWithoutAuthor = { ...attributes }; + delete attributesWithoutAuthor.author; + mapAttributesToMetadata(attributesWithoutAuthor, metadata, false); // Handle tags and keywords const tags = extractTagsFromAttributes(attributes); @@ -349,7 +492,7 @@ export function extractSectionMetadata(inputSectionContent: string): { metadata.tags = tags; } - const content = stripHeaderAndAttributes(inputSectionContent, true); + const content = stripSectionHeader(inputSectionContent); return { metadata, content, title }; } diff --git a/src/lib/utils/event_input_utils.ts b/src/lib/utils/event_input_utils.ts index 876fe31..cdeb501 100644 --- a/src/lib/utils/event_input_utils.ts +++ b/src/lib/utils/event_input_utils.ts @@ -170,6 +170,14 @@ export function validate30040EventSet(content: string): { function normalizeDTagValue(header: string): string { return header .toLowerCase() + // Decode common HTML entities first + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, " ") + // Then normalize as before .replace(/[^\p{L}\p{N}]+/gu, "-") .replace(/^-+|-+$/g, ""); } diff --git a/src/lib/utils/markup/advancedMarkupParser.ts b/src/lib/utils/markup/advancedMarkupParser.ts index f1973d9..3a4816c 100644 --- a/src/lib/utils/markup/advancedMarkupParser.ts +++ b/src/lib/utils/markup/advancedMarkupParser.ts @@ -30,6 +30,7 @@ function escapeHtml(text: string): string { const HEADING_REGEX = /^(#{1,6})\s+(.+)$/gm; const ALTERNATE_HEADING_REGEX = /^([^\n]+)\n(=+|-+)\n/gm; const INLINE_CODE_REGEX = /`([^`\n]+)`/g; +const MULTILINE_CODE_REGEX = /`([\s\S]*?)`/g; const HORIZONTAL_RULE_REGEX = /^(?:[-*_]\s*){3,}$/gm; const FOOTNOTE_REFERENCE_REGEX = /\[\^([^\]]+)\]/g; const FOOTNOTE_DEFINITION_REGEX = /^\[\^([^\]]+)\]:\s*(.+)$/gm; @@ -390,296 +391,41 @@ function restoreCodeBlocks(text: string, blocks: Map): string { } /** - * Process $...$ and $$...$$ math blocks: render as LaTeX if recognized, otherwise as AsciiMath - * This must run BEFORE any paragraph or inline code formatting. + * Process math expressions inside inline code blocks + * Only processes math that is inside backticks and contains $...$ or $$...$$ markings */ -function processDollarMath(content: string): string { - // Display math: $$...$$ (multi-line, not empty) - content = content.replace(/\$\$([\s\S]*?\S[\s\S]*?)\$\$/g, (_match, expr) => { - if (isLaTeXContent(expr)) { - return `
$$${expr}$$
`; - } else { - // Strip all $ or $$ from AsciiMath - const clean = expr.replace(/\$+/g, "").trim(); - return `
${clean}
`; +function processInlineCodeMath(content: string): string { + return content.replace(MULTILINE_CODE_REGEX, (match, codeContent) => { + // Check if the code content contains math expressions + const hasInlineMath = /\$((?:[^$\\]|\\.)*?)\$/.test(codeContent); + const hasDisplayMath = /\$\$[\s\S]*?\$\$/.test(codeContent); + + if (!hasInlineMath && !hasDisplayMath) { + // No math found, return the original inline code + return match; } - }); - // Inline math: $...$ (not empty, not just whitespace) - content = content.replace(/\$([^\s$][^$\n]*?)\$/g, (_match, expr) => { - if (isLaTeXContent(expr)) { - return `$${expr}$`; - } else { - const clean = expr.replace(/\$+/g, "").trim(); - return `${clean}`; + + // Process display math ($$...$$) first to avoid conflicts with inline math + let processedContent = codeContent.replace(/\$\$([\s\S]*?)\$\$/g, (mathMatch: string, mathContent: string) => { + // Skip empty math expressions + if (!mathContent.trim()) { + return mathMatch; } + return `\\[${mathContent}\\]`; }); - return content; -} - -/** - * Process LaTeX math expressions only within inline code blocks - */ -function processMathExpressions(content: string): string { - // Only process LaTeX within inline code blocks (backticks) - return content.replace(INLINE_CODE_REGEX, (_match, code) => { - const trimmedCode = code.trim(); - - // Check for unsupported LaTeX environments (like tabular) first - if (/\\begin\{tabular\}|\\\\begin\{tabular\}/.test(trimmedCode)) { - return `
-

- Unrendered, as it is LaTeX typesetting, not a formula: -

-
-          ${escapeHtml(trimmedCode)}
-        
-
`; - } - - // Check if the code contains LaTeX syntax - if (isLaTeXContent(trimmedCode)) { - // Detect LaTeX display math (\\[...\\]) - if (/^\\\[[\s\S]*\\\]$/.test(trimmedCode)) { - // Remove the delimiters for rendering - const inner = trimmedCode.replace(/^\\\[|\\\]$/g, ""); - return `
$$${inner}$$
`; - } - // Detect display math ($$...$$) - if (/^\$\$[\s\S]*\$\$$/.test(trimmedCode)) { - // Remove the delimiters for rendering - const inner = trimmedCode.replace(/^\$\$|\$\$$/g, ""); - return `
$$${inner}$$
`; - } - // Detect inline math ($...$) - if (/^\$[\s\S]*\$$/.test(trimmedCode)) { - // Remove the delimiters for rendering - const inner = trimmedCode.replace(/^\$|\$$/g, ""); - return `$${inner}$`; - } - // Default to inline math for any other LaTeX content - return `$${trimmedCode}$`; - } else { - // Check for edge cases that should remain as code, not math - // These patterns indicate code that contains dollar signs but is not math - const codePatterns = [ - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=/, // Variable assignment like "const price =" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/, // Function call like "echo(" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\{/, // Object literal like "const obj = {" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\[/, // Array literal like "const arr = [" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*!&|^~]/, // Operator like "const x = 1 +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Two identifiers like "const price = amount" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]/, // Number like "const x = 1" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]/, // Complex expression like "const price = amount +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[a-zA-Z0-9_$]*\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Three identifiers like "const price = amount + tax" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]/, // Two identifiers and number like "const price = amount + 1" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]/, // Identifier, number, operator like "const x = 1 +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Identifier, number, identifier like "const x = 1 + y" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[0-9]/, // Identifier, number, number like "const x = 1 + 2" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Complex like "const x = 1 + y" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]/, // Complex like "const x = 1 + 2" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]/, // Very complex like "const x = 1 + y +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Very complex like "const x = 1 + y + z" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]\s*[0-9]/, // Very complex like "const x = 1 + y + 2" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]\s*[+\-*/%=<>!&|^~]/, // Very complex like "const x = 1 + 2 +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Very complex like "const x = 1 + 2 + y" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]/, // Very complex like "const x = 1 + 2 + 3" - // Additional patterns for JavaScript template literals and other code - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*`/, // Template literal assignment like "const str = `" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*'/, // String assignment like "const str = '" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*"/, // String assignment like "const str = \"" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[0-9]/, // Number assignment like "const x = 1" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Variable assignment like "const x = y" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[+\-*/%=<>!&|^~]/, // Assignment with operator like "const x = +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]/, // Assignment with variable and operator like "const x = y +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Assignment with two variables and operator like "const x = y + z" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[0-9]\s*[+\-*/%=<>!&|^~]/, // Assignment with number and operator like "const x = 1 +" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[a-zA-Z_$][a-zA-Z0-9_$]*/, // Assignment with number, operator, variable like "const x = 1 + y" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*[+\-*/%=<>!&|^~]\s*[0-9]/, // Assignment with variable, operator, number like "const x = y + 1" - /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*=\s*[0-9]\s*[+\-*/%=<>!&|^~]\s*[0-9]/, // Assignment with number, operator, number like "const x = 1 + 2" - ]; - - // If it matches code patterns, treat as regular code - if (codePatterns.some((pattern) => pattern.test(trimmedCode))) { - const escapedCode = trimmedCode - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - return `${escapedCode}`; - } - - // Return as regular inline code - const escapedCode = trimmedCode - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - return `${escapedCode}`; + + // Process inline math ($...$) after display math + // Use a more sophisticated regex that handles escaped dollar signs + processedContent = processedContent.replace(/\$((?:[^$\\]|\\.)*?)\$/g, (mathMatch: string, mathContent: string) => { + // Skip empty math expressions + if (!mathContent.trim()) { + return mathMatch; } + return `\\(${mathContent}\\)`; + }); + + return `\`${processedContent}\``; }); -} - -/** - * Checks if content contains LaTeX syntax - */ -function isLaTeXContent(content: string): boolean { - const trimmed = content.trim(); - - // Check for simple math expressions first (like AsciiMath) - if (/^\$[^$]+\$$/.test(trimmed)) { - return true; - } - - // Check for display math - if (/^\$\$[\s\S]*\$\$$/.test(trimmed)) { - return true; - } - - // Check for LaTeX display math - if (/^\\\[[\s\S]*\\\]$/.test(trimmed)) { - return true; - } - - // Check for LaTeX environments with double backslashes (like tabular) - if (/\\\\begin\{[^}]+\}/.test(trimmed) || /\\\\end\{[^}]+\}/.test(trimmed)) { - return true; - } - - // Check for common LaTeX patterns - const latexPatterns = [ - /\\[a-zA-Z]+/, // LaTeX commands like \frac, \sum, etc. - /\\\\[a-zA-Z]+/, // LaTeX commands with double backslashes like \\frac, \\sum, etc. - /\\[\(\)\[\]]/, // LaTeX delimiters like \(, \), \[, \] - /\\\\[\(\)\[\]]/, // LaTeX delimiters with double backslashes like \\(, \\), \\[, \\] - /\\\[[\s\S]*?\\\]/, // LaTeX display math \[ ... \] - /\\\\\[[\s\S]*?\\\\\]/, // LaTeX display math with double backslashes \\[ ... \\] - /\\begin\{/, // LaTeX environments - /\\\\begin\{/, // LaTeX environments with double backslashes - /\\end\{/, // LaTeX environments - /\\\\end\{/, // LaTeX environments with double backslashes - /\\begin\{array\}/, // LaTeX array environment - /\\\\begin\{array\}/, // LaTeX array environment with double backslashes - /\\end\{array\}/, - /\\\\end\{array\}/, - /\\begin\{matrix\}/, // LaTeX matrix environment - /\\\\begin\{matrix\}/, // LaTeX matrix environment with double backslashes - /\\end\{matrix\}/, - /\\\\end\{matrix\}/, - /\\begin\{bmatrix\}/, // LaTeX bmatrix environment - /\\\\begin\{bmatrix\}/, // LaTeX bmatrix environment with double backslashes - /\\end\{bmatrix\}/, - /\\\\end\{bmatrix\}/, - /\\begin\{pmatrix\}/, // LaTeX pmatrix environment - /\\\\begin\{pmatrix\}/, // LaTeX pmatrix environment with double backslashes - /\\end\{pmatrix\}/, - /\\\\end\{pmatrix\}/, - /\\begin\{tabular\}/, // LaTeX tabular environment - /\\\\begin\{tabular\}/, // LaTeX tabular environment with double backslashes - /\\end\{tabular\}/, - /\\\\end\{tabular\}/, - /\$\$/, // Display math delimiters - /\$[^$]+\$/, // Inline math delimiters - /\\text\{/, // LaTeX text command - /\\\\text\{/, // LaTeX text command with double backslashes - /\\mathrm\{/, // LaTeX mathrm command - /\\\\mathrm\{/, // LaTeX mathrm command with double backslashes - /\\mathbf\{/, // LaTeX bold command - /\\\\mathbf\{/, // LaTeX bold command with double backslashes - /\\mathit\{/, // LaTeX italic command - /\\\\mathit\{/, // LaTeX italic command with double backslashes - /\\sqrt/, // Square root - /\\\\sqrt/, // Square root with double backslashes - /\\frac/, // Fraction - /\\\\frac/, // Fraction with double backslashes - /\\sum/, // Sum - /\\\\sum/, // Sum with double backslashes - /\\int/, // Integral - /\\\\int/, // Integral with double backslashes - /\\lim/, // Limit - /\\\\lim/, // Limit with double backslashes - /\\infty/, // Infinity - /\\\\infty/, // Infinity with double backslashes - /\\alpha/, // Greek letters - /\\\\alpha/, // Greek letters with double backslashes - /\\beta/, - /\\\\beta/, - /\\gamma/, - /\\\\gamma/, - /\\delta/, - /\\\\delta/, - /\\theta/, - /\\\\theta/, - /\\lambda/, - /\\\\lambda/, - /\\mu/, - /\\\\mu/, - /\\pi/, - /\\\\pi/, - /\\sigma/, - /\\\\sigma/, - /\\phi/, - /\\\\phi/, - /\\omega/, - /\\\\omega/, - /\\partial/, // Partial derivative - /\\\\partial/, // Partial derivative with double backslashes - /\\nabla/, // Nabla - /\\\\nabla/, // Nabla with double backslashes - /\\cdot/, // Dot product - /\\\\cdot/, // Dot product with double backslashes - /\\times/, // Times - /\\\\times/, // Times with double backslashes - /\\div/, // Division - /\\\\div/, // Division with double backslashes - /\\pm/, // Plus-minus - /\\\\pm/, // Plus-minus with double backslashes - /\\mp/, // Minus-plus - /\\\\mp/, // Minus-plus with double backslashes - /\\leq/, // Less than or equal - /\\\\leq/, // Less than or equal with double backslashes - /\\geq/, // Greater than or equal - /\\\\geq/, // Greater than or equal with double backslashes - /\\neq/, // Not equal - /\\\\neq/, // Not equal with double backslashes - /\\approx/, // Approximately equal - /\\\\approx/, // Approximately equal with double backslashes - /\\equiv/, // Equivalent - /\\\\equiv/, // Equivalent with double backslashes - /\\propto/, // Proportional - /\\\\propto/, // Proportional with double backslashes - /\\in/, // Element of - /\\\\in/, // Element of with double backslashes - /\\notin/, // Not element of - /\\\\notin/, // Not element of with double backslashes - /\\subset/, // Subset - /\\\\subset/, // Subset with double backslashes - /\\supset/, // Superset - /\\\\supset/, // Superset with double backslashes - /\\cup/, // Union - /\\\\cup/, // Union with double backslashes - /\\cap/, // Intersection - /\\\\cap/, // Intersection with double backslashes - /\\emptyset/, // Empty set - /\\\\emptyset/, // Empty set with double backslashes - /\\mathbb\{/, // Blackboard bold - /\\\\mathbb\{/, // Blackboard bold with double backslashes - /\\mathcal\{/, // Calligraphic - /\\\\mathcal\{/, // Calligraphic with double backslashes - /\\mathfrak\{/, // Fraktur - /\\\\mathfrak\{/, // Fraktur with double backslashes - /\\mathscr\{/, // Script - /\\\\mathscr\{/, // Script with double backslashes - ]; - - return latexPatterns.some((pattern) => pattern.test(trimmed)); } /** @@ -693,11 +439,8 @@ export async function parseAdvancedmarkup(text: string): Promise { const { text: withoutCode, blocks } = processCodeBlocks(text); let processedText = withoutCode; - // Step 2: Process $...$ and $$...$$ math blocks (LaTeX or AsciiMath) - processedText = processDollarMath(processedText); - - // Step 3: Process LaTeX math expressions ONLY within inline code blocks (legacy support) - processedText = processMathExpressions(processedText); + // Step 2: Process math inside inline code blocks + processedText = processInlineCodeMath(processedText); // Step 4: Process block-level elements (tables, headings, horizontal rules) // AI-NOTE: 2025-01-24 - Removed duplicate processBlockquotes call to fix image rendering issues diff --git a/test_data/LaTeXtestfile.json b/test_data/LaTeXtestfile.json deleted file mode 100644 index 1dc63f1..0000000 --- a/test_data/LaTeXtestfile.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "created_at": 1752150799, - "content": "# This is a test file for writing mathematical formulas in #NostrMarkup\n\nThis document covers the rendering of formulas in TeX/LaTeX and AsciiMath notation, or some combination of those within the same page. It is meant to be rendered by clients utilizing MathJax.\n\nIf you want the entire document to be rendered as mathematics, place the entire thing in a back-tick code-block, but know that this makes the document slower to load, it is harder to format the prose, and the result is less legible. It also doesn't increase portability, as it's easy to export markup as LaTeX files, or as PDFs, with the formulas rendered.\n\nThe general idea, is that anything placed within `single back-ticks` is inline code, and inline-code will all be scanned for typical mathematics statements and rendered with best-effort. (For more precise rendering, use AsciiDoc.) We will not render text that is not marked as inline code, as mathematical formulas, as that is prose.\n\nIf you want the TeX to be blended into the surrounding text, wrap the text within single `$`. Otherwise, use double `$$` symbols, for display math, and it will appear on its own line.\n\n## TeX Examples\n\nInline equation: `$\\sqrt{x}$`\n\nSame equation, in the display mode: `$$\\sqrt{x}$$`\n\nSomething more complex, inline: `$\\mathbb{N} = \\{ a \\in \\mathbb{Z} : a > 0 \\}$`\n\nSomething complex, in display mode: `$$P \\left( A=2 \\, \\middle| \\, \\dfrac{A^2}{B}>4 \\right)$$`\n\nAnother example of `$$\\prod_{i=1}^{n} x_i - 1$$` inline formulas.\n\nFunction example: \n`$$\nf(x)=\n\\begin{cases}\n1/d_{ij} & \\quad \\text{when $d_{ij} \\leq 160$}\\\\ \n0 & \\quad \\text{otherwise}\n\\end{cases}\n$$`\n\nAnd a matrix:\n`$$\nM = \n\\begin{bmatrix}\n\\frac{5}{6} & \\frac{1}{6} & 0 \\\\[0.3em]\n\\frac{5}{6} & 0 & \\frac{1}{6} \\\\[0.3em]\n0 & \\frac{5}{6} & \\frac{1}{6}\n\\end{bmatrix}\n$$`\n\nLaTeX ypesetting won't be rendered. Use NostrMarkup delimeter tables for this sort of thing.\n\n`\\\\begin{tabular}{|c|c|c|l|r|}\n\\\\hline\n\\\\multicolumn{3}{|l|}{test} & A & B \\\\\\\\\n\\\\hline\n1 & 2 & 3 & 4 & 5 \\\\\\\\\n\\\\hline\n\\\\end{tabular}`\n\nWe also recognize common LaTeX statements:\n\n`\\[\n\\begin{array}{ccccc}\n1 & 2 & 3 & 4 & 5 \\\\\n\\end{array}\n\\]`\n\n`\\[ x^n + y^n = z^n \\]`\n\n`\\sqrt{x^2+1}`\n\nGreek letters are a snap: `$\\Psi$`, `$\\psi$`, `$\\Phi$`, `$\\phi$`. \n\nEquations within text are easy--- A well known Maxwell thermodynamic relation is `$\\left.{\\partial T \\over \\partial P}\\right|_{s} = \\left.{\\partial v \\over \\partial s}\\right|_{P}$`.\n\nYou can also set aside equations like so: `\\begin{eqnarray} du &=& T\\ ds -P\\ dv, \\qquad \\mbox{first law.}\\label{fl}\\\\ ds &\\ge& {\\delta q \\over T}.\\qquad \\qquad \\mbox{second law.} \\label{sl} \\end {eqnarray}`\n\n## And some good ole Asciimath\n\nAsciimath doesn't use `$` or `$$` delimiters, but we are using it to make mathy stuff easier to find. If you want it inline, include it inline. If you want it on a separate line, put a hard-return before and after.\n\nInline text example here `$E=mc^2$` and another `$1/(x+1)$`; very simple.\n\nDisplaying on a separate line:\n\n`$$sum_(k=1)^n k = 1+2+ cdots +n=(n(n+1))/2$$`\n\n`$$int_0^1 x^2 dx$$`\n\n`$$x = (-6 +- sqrt((-6)^2 - 4 (1)(4)))/(2 xx 1)$$`\n\n`$$|x|= {(x , if x ge 0 text(,)),(-x , if x <0.):}$$`\n\nDisplaying with wider spacing:\n\n`$a=3, \\ \\ \\ b=-3,\\ \\ $` and `$ \\ \\ c=2$`.\n\nThus `$(a+b)(c+b)=0$`.\n\nDisplaying with indentations:\n\nUsing the quadratic formula, the roots of `$x^2-6x+4=0$` are\n\n`$$x = (-6 +- sqrt((-6)^2 - 4 (1)(4)))/(2 xx 1)$$`\n\n`$$ \\ \\ = (-6 +- sqrt(36 - 16))/2$$`\n\n`$$ \\ \\ =(-6 +- sqrt(20))/2$$`\n\n`$$ \\ \\ = -0.8 or 2.2 \\ \\ \\ $$` to 1 decimal place.\n\nAdvanced alignment and matrices looks like this:\n\nA `$3xx3$` matrix, `$$((1,2,3),(4,5,6),(7,8,9))$$` and a `$2xx1$` matrix, or vector, `$$((1),(0))$$`.\n\nThe outer brackets determine the delimiters e.g. `$|(a,b),(c,d)|=ad-bc$`.\n\nA general `$m xx n$` matrix `$$((a_(11), cdots , a_(1n)),(vdots, ddots, vdots),(a_(m1), cdots , a_(mn)))$$`\n\n## Mixed Examples\n\nHere are some examples mixing LaTeX and AsciiMath:\n\n- LaTeX inline: `$\\frac{1}{2}$` vs AsciiMath inline: `$1/2$`\n- LaTeX display: `$$\\sum_{i=1}^n x_i$$` vs AsciiMath display: `$$sum_(i=1)^n x_i$$`\n- LaTeX matrix: `$$\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}$$` vs AsciiMath matrix: `$$((a,b),(c,d))$$`\n\n## Edge Cases\n\n- Empty math: `$$`\n- Just delimiters: `$ $`\n- Dollar signs in text: The price is $10.50\n- Currency: `$19.99`\n- Shell command: `echo \"Price: $100\"`\n- JavaScript template: `const price = \\`$${amount}\\``\n- CSS with dollar signs: `color: $primary-color`\n\nThis document should demonstrate that:\n1. LaTeX is processed within inline code blocks with proper delimiters\n2. AsciiMath is processed within inline code blocks with proper delimiters\n3. Regular code blocks remain unchanged\n4. Mixed content is handled correctly\n5. Edge cases are handled gracefully", - "tags": [ - ["t", "test"], - ["t", "Asciimath"], - ["t", "TeX"], - ["t", "LaTeX"], - [ - "d", - "this-is-a-test-file-for-writing-mathematical-formulas-in-nostrmarkup" - ], - [ - "title", - "This is a test file for writing mathematical formulas in #NostrMarkup" - ] - ], - "kind": 30023, - "pubkey": "fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1", - "id": "91be487e67cb68cfe3c7e965a654642b7bcedecb68340523a8c1b865b21fa5dc", - "sig": "59b7f87fe2c2d318152cf5b4796580f79a26936d515a816ddcb89b89ba337992eaa3d50896d3bde345d25be99c9caa3a237d476abeb8537589256cbcceeb2e75" -} diff --git a/test_data/LaTeXtestfile.md b/test_data/LaTeXtestfile.md deleted file mode 100644 index 01e6264..0000000 --- a/test_data/LaTeXtestfile.md +++ /dev/null @@ -1,151 +0,0 @@ -# This is a testfile for writing mathematic formulas in NostrMarkup - -This document covers the rendering of formulas in TeX/LaTeX and AsciiMath -notation, or some combination of those within the same page. It is meant to be -rendered by clients utilizing MathJax. - -If you want the entire document to be rendered as mathematics, place the entire -thing in a backtick-codeblock, but know that this makes the document slower to -load, it is harder to format the prose, and the result is less legible. It also -doesn't increase portability, as it's easy to export markup as LaTeX files, or -as PDFs, with the formulas rendered. - -The general idea, is that anything placed within `single backticks` is inline -code, and inline-code will all be scanned for typical mathematics statements and -rendered with best-effort. (For more precise rendering, use Asciidoc.) We will -not render text that is not marked as inline code, as mathematical formulas, as -that is prose. - -If you want the TeX to be blended into the surrounding text, wrap the text -within single `$`. Otherwise, use double `$$` symbols, for display math, and it -will appear on its own line. - -## TeX Examples - -Inline equation: `$\sqrt{x}$` - -Same equation, in the display mode: `$$\sqrt{x}$$` - -Something more complex, inline: `$\mathbb{N} = \{ a \in \mathbb{Z} : a > 0 \}$` - -Something complex, in display mode: -`$$P \left( A=2 \, \middle| \, \dfrac{A^2}{B}>4 \right)$$` - -Another example of `$$\prod_{i=1}^{n} x_i - 1$$` inline formulas. - -Function example: `$$ f(x)= \begin{cases} 1/d_{ij} & \quad \text{when -$d_{ij} \leq 160$}\\ 0 & \quad \text{otherwise} \end{cases} - -$$ ` - -And a matrix: ` $$ - -M = \begin{bmatrix} \frac{5}{6} & \frac{1}{6} & 0 \\[0.3em] \frac{5}{6} & 0 & -\frac{1}{6} \\[0.3em] 0 & \frac{5}{6} & \frac{1}{6} \end{bmatrix} - -$$ ` - -LaTeX ypesetting won't be rendered. Use NostrMarkup delimeter tables for this -sort of thing. - -`\\begin{tabular}{|c|c|c|l|r|} -\\hline -\\multicolumn{3}{|l|}{test} & A & B \\\\ -\\hline -1 & 2 & 3 & 4 & 5 \\\\ -\\hline -\\end{tabular}` - -We also recognize common LaTeX statements: - -`\[ -\begin{array}{ccccc} -1 & 2 & 3 & 4 & 5 \\ -\end{array} -\]` - -`\[ x^n + y^n = z^n \]` - -`\sqrt{x^2+1}` - -Greek letters are a snap: `$\Psi$`, `$\psi$`, `$\Phi$`, `$\phi$`. - -Equations within text are easy--- A well known Maxwell thermodynamic relation is -`$\left.{\partial T \over \partial P}\right|_{s} = \left.{\partial v \over \partial s}\right|_{P}$`. - -You can also set aside equations like so: -`\begin{eqnarray} du &=& T\ ds -P\ dv, \qquad \mbox{first law.}\label{fl}\\ ds &\ge& {\delta q \over T}.\qquad \qquad \mbox{second law.} \label{sl} \end {eqnarray}` - -## And some good ole Asciimath - -Asciimath doesn't use `$` or `$$` delimiters, but we are using it to make mathy -stuff easier to find. If you want it inline, include it inline. If you want it -on a separate line, put a hard-return before and after. - -Inline text example here `$E=mc^2$` and another `$1/(x+1)$`; very simple. - -Displaying on a separate line: - -`$$sum_(k=1)^n k = 1+2+ cdots +n=(n(n+1))/2$$` - -`$$int_0^1 x^2 dx$$` - -`$$x = (-6 +- sqrt((-6)^2 - 4 (1)(4)))/(2 xx 1)$$` - -`$$|x|= {(x , if x ge 0 text(,)),(-x , if x <0.):}$$` - -Displaying with wider spacing: - -`$a=3, \ \ \ b=-3,\ \ $` and `$ \ \ c=2$`. - -Thus `$(a+b)(c+b)=0$`. - -Displaying with indentations: - -Using the quadratic formula, the roots of `$x^2-6x+4=0$` are - -`$$x = (-6 +- sqrt((-6)^2 - 4 (1)(4)))/(2 xx 1)$$` - -`$$ \ \ = (-6 +- sqrt(36 - 16))/2$$` - -`$$ \ \ =(-6 +- sqrt(20))/2$$` - -`$$ \ \ = -0.8 or 2.2 \ \ \ $$` to 1 decimal place. - -Advanced alignment and matrices looks like this: - -A `$3xx3$` matrix, `$$((1,2,3),(4,5,6),(7,8,9))$$` and a `$2xx1$` matrix, or -vector, `$$((1),(0))$$`. - -The outer brackets determine the delimiters e.g. `$|(a,b),(c,d)|=ad-bc$`. - -A general `$m xx n$` matrix -`$$((a_(11), cdots , a_(1n)),(vdots, ddots, vdots),(a_(m1), cdots , a_(mn)))$$` - -## Mixed Examples - -Here are some examples mixing LaTeX and AsciiMath: - -- LaTeX inline: `$\frac{1}{2}$` vs AsciiMath inline: `$1/2$` -- LaTeX display: `$$\sum_{i=1}^n x_i$$` vs AsciiMath display: - `$$sum_(i=1)^n x_i$$` -- LaTeX matrix: `$$\begin{pmatrix} a & b \\ c & d \end{pmatrix}$$` vs AsciiMath - matrix: `$$((a,b),(c,d))$$` - -## Edge Cases - -- Empty math: `$$` -- Just delimiters: `$ $` -- Dollar signs in text: The price is $10.50 -- Currency: `$19.99` -- Shell command: `echo "Price: $100"` -- JavaScript template: `const price = \`$${amount}\`` -- CSS with dollar signs: `color: $primary-color` - -This document should demonstrate that: - -1. LaTeX is processed within inline code blocks with proper delimiters -2. AsciiMath is processed within inline code blocks with proper delimiters -3. Regular code blocks remain unchanged -4. Mixed content is handled correctly -5. Edge cases are handled gracefully $$ diff --git a/test_output.log b/test_output.log new file mode 100644 index 0000000..ce551de --- /dev/null +++ b/test_output.log @@ -0,0 +1,4178 @@ + +> alexandria@0.0.2 test +> vitest + + + DEV v3.2.4 /home/madmin/Projects/GitCitadel/gc-alexandria + + ✓ tests/unit/ZettelEditor.test.ts (24 tests) 20ms +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Addressable Event Deduplication > should keep only the most recent version of addressable events by coordinate +[eventDeduplication] Found 1 duplicate events out of 3 total events +[eventDeduplication] Reduced to 2 unique coordinates +[eventDeduplication] Duplicate details: [ + { + coordinate: '30041:pubkey1:chapter-1', + count: 2, + events: [ 'event1 (created_at: 1000)', 'event2 (created_at: 2000)' ] + } +] + +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Addressable Event Deduplication > should handle events with missing d-tags gracefully +[eventDeduplication] No duplicates found in 1 events + +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Addressable Event Deduplication > should handle events with missing timestamps +[eventDeduplication] Found 1 duplicate events out of 2 total events +[eventDeduplication] Reduced to 1 unique coordinates +[eventDeduplication] Duplicate details: [ + { + coordinate: '30041:pubkey1:chapter-3', + count: 2, + events: [ 'event7 (created_at: 0)', 'event8 (created_at: 1500)' ] + } +] + +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Mixed Event Type Deduplication > should only deduplicate addressable events (kinds 30000-39999) +[eventDeduplication] deduplicateAndCombineEvents: Found 1 duplicate coordinates out of 4 replaceable events +[eventDeduplication] deduplicateAndCombineEvents: Reduced from 5 to 4 events (1 removed) +[eventDeduplication] deduplicateAndCombineEvents: Duplicate details: [ + { + coordinate: '30041:pubkey1:chapter-1', + count: 2, + events: [ 'event1 (created_at: 1000)', 'event2 (created_at: 2000)' ] + } +] + +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Edge Cases > should handle events with null/undefined values +[eventDeduplication] No duplicates found in 1 events + +stdout | tests/unit/relayDeduplication.test.ts > Relay Deduplication Behavior Tests > Edge Cases > should handle events from different authors with same d-tag +[eventDeduplication] No duplicates found in 2 events + +stdout | tests/unit/relayDeduplication.test.ts > Relay Behavior Simulation > should simulate what happens when relays return duplicate events +[eventDeduplication] Found 2 duplicate events out of 3 total events +[eventDeduplication] Reduced to 1 unique coordinates +[eventDeduplication] Duplicate details: [ + { + coordinate: '30041:pubkey1:chapter-1', + count: 3, + events: [ + 'event1 (created_at: 1000)', + 'event2 (created_at: 2000)', + 'event3 (created_at: 1500)' + ] + } +] + +stdout | tests/unit/relayDeduplication.test.ts > Relay Behavior Simulation > should simulate multiple relays returning different versions +[eventDeduplication] Found 1 duplicate events out of 2 total events +[eventDeduplication] Reduced to 1 unique coordinates +[eventDeduplication] Duplicate details: [ + { + coordinate: '30041:pubkey1:chapter-1', + count: 2, + events: [ 'event1 (created_at: 1000)', 'event2 (created_at: 2000)' ] + } +] + +stdout | tests/unit/relayDeduplication.test.ts > Real Relay Deduplication Tests > should detect if relays are returning duplicate replaceable events +Note: This test would require actual relay queries to verify deduplication behavior +To run this test properly, we would need to: +1. Query real relays for replaceable events +2. Check if relays return duplicates +3. Verify our deduplication logic works on real data + +stdout | tests/unit/relayDeduplication.test.ts > Real Relay Deduplication Tests > should verify that our deduplication logic works on real relay data +Note: This test would require actual relay queries +To implement this test, we would need to: +1. Set up NDK with real relays +2. Fetch events for a known author with multiple versions +3. Apply deduplication and verify results + +stdout | tests/unit/relayDeduplication.test.ts > Practical Relay Behavior Analysis > should document what we know about relay deduplication behavior + +=== RELAY DEDUPLICATION BEHAVIOR ANALYSIS === + +Based on the code analysis and the comment from onedev: + +1. THEORETICAL BEHAVIOR: + - Relays SHOULD handle deduplication for replaceable events + - Only the most recent version of each coordinate should be stored + - Client-side deduplication should only be needed for cached/local events + +2. REALITY CHECK: + - Not all relays implement deduplication correctly + - Some relays may return multiple versions of the same event + - Network conditions and relay availability can cause inconsistencies + +3. ALEXANDRIA'S APPROACH: + - Implements client-side deduplication as a safety net + - Uses coordinate system (kind:pubkey:d-tag) for addressable events + - Keeps the most recent version based on created_at timestamp + - Only applies to replaceable events (kinds 30000-39999) + +4. WHY KEEP THE DEDUPLICATION: + - Defensive programming against imperfect relay implementations + - Handles multiple relay sources with different data + - Works with cached events that might be outdated + - Ensures consistent user experience regardless of relay behavior + +5. TESTING STRATEGY: + - Unit tests verify our deduplication logic works correctly + - Integration tests would verify relay behavior (when network allows) + - Monitoring can help determine if relays improve over time + +stdout | tests/unit/relayDeduplication.test.ts > Practical Relay Behavior Analysis > should provide recommendations for when to remove deduplication + +=== RECOMMENDATIONS FOR REMOVING DEDUPLICATION === + +The deduplication logic should be kept until: + +1. RELAY STANDARDS: + - NIP-33 (replaceable events) is widely implemented by relays + - Relays consistently return only the most recent version + - No major relay implementations return duplicates + +2. TESTING EVIDENCE: + - Real-world testing shows relays don't return duplicates + - Multiple relay operators confirm deduplication behavior + - No user reports of duplicate content issues + +3. MONITORING: + - Add logging to track when deduplication is actually used + - Monitor relay behavior over time + - Collect metrics on duplicate events found + +4. GRADUAL REMOVAL: + - Make deduplication configurable (on/off) + - Test with deduplication disabled in controlled environments + - Monitor for issues before removing completely + +5. FALLBACK STRATEGY: + - Keep deduplication as a fallback option + - Allow users to enable it if they experience issues + - Maintain the code for potential future use + + ✓ tests/unit/relayDeduplication.test.ts (22 tests) 22ms + ✓ tests/unit/nostr_identifiers.test.ts (12 tests) 9ms + ✓ tests/unit/tagExpansion.test.ts (12 tests) 23ms +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure with Preamble > should build 30040 event set with preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document with Preamble', + authors: [ 'John Doe', 'Section Author' ], + version: '1.0', + publicationDate: '2024-01-15, Alexandria Test', + summary: 'This is a test document with preamble', + tags: [ 'test', 'preamble', 'asciidoc' ] + }, + content: '= Test Document with Preamble\n' + + 'John Doe \n' + + '1.0, 2024-01-15, Alexandria Test\n' + + ':summary: This is a test document with preamble\n' + + ':keywords: test, preamble, asciidoc\n' + + '\n' + + 'This is the preamble content that should be included.\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document with Preamble', + indexDTag: 'test-document-with-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-with-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-with-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document with Preamble' ], + [ 'author', 'John Doe' ], + [ 'author', 'Section Author' ], + [ 'version', '1.0' ], + [ 'published_on', '2024-01-15, Alexandria Test' ], + [ 'summary', 'This is a test document with preamble' ], + [ 't', 'test' ], + [ 't', 'preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-with-preamble' ], + [ 'title', 'Test Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure without Preamble > should build 30040 event set without preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document without Preamble', + authors: [ 'Section Author' ], + version: 'Version', + summary: 'This is a test document without preamble', + tags: [ 'test', 'no-preamble', 'asciidoc' ] + }, + content: '= Test Document without Preamble\n' + + ':summary: This is a test document without preamble\n' + + ':keywords: test, no-preamble, asciidoc\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document without Preamble', + indexDTag: 'test-document-without-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-without-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-without-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document without Preamble' ], + [ 'author', 'Section Author' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a test document without preamble' ], + [ 't', 'test' ], + [ 't', 'no-preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-without-preamble' ], + [ 'title', 'Test Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ❯ tests/unit/metadataExtraction.test.ts (16 tests | 2 failed) 231ms + × AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly 124ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract section metadata correctly 8ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract standalone author names and remove them from content 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should handle multiple standalone author names 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should not extract non-author lines as authors 4ms + ✓ AsciiDoc Metadata Extraction > parseAsciiDocWithMetadata should parse complete document 23ms + ✓ AsciiDoc Metadata Extraction > metadataToTags should convert metadata to Nostr tags 2ms + ✓ AsciiDoc Metadata Extraction > should handle index card format correctly 4ms + ✓ AsciiDoc Metadata Extraction > should handle empty content gracefully 4ms + ✓ AsciiDoc Metadata Extraction > should handle keywords as tags 4ms + ✓ AsciiDoc Metadata Extraction > should handle both tags and keywords 5ms + ✓ AsciiDoc Metadata Extraction > should handle tags only 8ms + ✓ AsciiDoc Metadata Extraction > should handle both summary and description 15ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle section-only content correctly 8ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle minimal document header (just title) correctly 1ms + × AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly 7ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure with Preamble > should build 30040 event set with skeleton structure and preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document with Preamble', + version: 'Version', + summary: 'This is a skeleton document with preamble', + tags: [ 'skeleton', 'preamble', 'empty' ] + }, + content: '= Skeleton Document with Preamble\n' + + ':summary: This is a skeleton document with preamble\n' + + ':keywords: skeleton, preamble, empty\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document with Preamble', + indexDTag: 'skeleton-document-with-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-with-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-with-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-with-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document with preamble' ], + [ 't', 'skeleton' ], + [ 't', 'preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-with-preamble' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure without Preamble > should build 30040 event set with skeleton structure without preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document without Preamble', + version: 'Version', + summary: 'This is a skeleton document without preamble', + tags: [ 'skeleton', 'no-preamble', 'empty' ] + }, + content: '= Skeleton Document without Preamble\n' + + ':summary: This is a skeleton document without preamble\n' + + ':keywords: skeleton, no-preamble, empty\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document without Preamble', + indexDTag: 'skeleton-document-without-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-without-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-without-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-without-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document without preamble' ], + [ 't', 'skeleton' ], + [ 't', 'no-preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-without-preamble' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card format +Parsed AsciiDoc: { + metadata: { title: 'Test Index Card', version: 'Version' }, + content: '= Test Index Card\nindex card', + sections: [] +} +Creating index card format (no sections) + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card with metadata +Parsed AsciiDoc: { + metadata: { + title: 'Test Index Card with Metadata', + version: 'Version', + summary: 'This is an index card with metadata', + tags: [ 'index', 'card', 'metadata' ] + }, + content: '= Test Index Card with Metadata\n' + + ':summary: This is an index card with metadata\n' + + ':keywords: index, card, metadata\n' + + 'index card', + sections: [] +} +Index event: { + documentTitle: 'Test Index Card with Metadata', + indexDTag: 'test-index-card-with-metadata' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'index-card' ], + [ 'title', 'Test Index Card with Metadata' ], + [ 'version', 'Version' ], + [ 'summary', 'This is an index card with metadata' ], + [ 't', 'index' ], + [ 't', 'card' ], + [ 't', 'metadata' ], + [ 'd', 'test-index-card-with-metadata' ], + [ 'title', 'Test Index Card with Metadata' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Complex Metadata Structures > should handle complex metadata with all attribute types +Parsed AsciiDoc: { + metadata: { + title: 'Complex Metadata Document', + authors: [ + 'Jane Smith', + 'Override Author', + 'Third Author', + 'Section Author', + 'Section Co-Author' + ], + version: '2.0', + publicationDate: '2024-03-01', + summary: 'This is a complex document with all metadata types Alternative description field', + publishedBy: 'Alexandria Complex', + type: 'book', + coverImage: 'https://example.com/cover.jpg', + isbn: '978-0-123456-78-9', + source: 'https://github.com/alexandria/complex', + autoUpdate: 'yes', + tags: [ + 'additional', + 'tags', + 'here', + 'complex', + 'metadata', + 'all-types' + ] + }, + content: '= Complex Metadata Document\n' + + 'Jane Smith \n' + + '2.0, 2024-02-20, Alexandria Complex\n' + + ':summary: This is a complex document with all metadata types\n' + + ':description: Alternative description field\n' + + ':keywords: complex, metadata, all-types\n' + + ':tags: additional, tags, here\n' + + ':author: Override Author\n' + + ':author: Third Author\n' + + ':version: 3.0\n' + + ':published_on: 2024-03-01\n' + + ':published_by: Alexandria Complex\n' + + ':type: book\n' + + ':image: https://example.com/cover.jpg\n' + + ':isbn: 978-0-123456-78-9\n' + + ':source: https://github.com/alexandria/complex\n' + + ':auto-update: yes\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Section with Complex Metadata\n' + + ':author: Section Author\n' + + ':author: Section Co-Author\n' + + ':summary: This section has complex metadata\n' + + ':description: Alternative description for section\n' + + ':keywords: section, complex, metadata\n' + + ':tags: section, tags\n' + + ':type: chapter\n' + + ':image: https://example.com/section-image.jpg\n' + + '\n' + + 'This is the section content.', + sections: [ + { + metadata: [Object], + content: 'This is the section content.', + title: 'Section with Complex Metadata' + } + ] +} +Index event: { + documentTitle: 'Complex Metadata Document', + indexDTag: 'complex-metadata-document' +} +Creating section 0: { + title: 'Section with Complex Metadata', + dTag: 'complex-metadata-document-section-with-complex-metadata', + content: 'This is the section content.', + metadata: { + title: 'Section with Complex Metadata', + authors: [ 'Section Author', 'Section Co-Author' ], + summary: 'This section has complex metadata Alternative description for section', + type: 'chapter', + coverImage: 'https://example.com/section-image.jpg', + tags: [ 'section', 'tags', 'complex', 'metadata' ] + } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'complex' ], + [ 'title', 'Complex Metadata Document' ], + [ 'author', 'Jane Smith' ], + [ 'author', 'Override Author' ], + [ 'author', 'Third Author' ], + [ 'author', 'Section Author' ], + [ 'author', 'Section Co-Author' ], + [ 'version', '2.0' ], + [ 'published_on', '2024-03-01' ], + [ 'published_by', 'Alexandria Complex' ], + [ + 'summary', + 'This is a complex document with all metadata types Alternative description field' + ], + [ 'image', 'https://example.com/cover.jpg' ], + [ 'i', '978-0-123456-78-9' ], + [ 'source', 'https://github.com/alexandria/complex' ], + [ 'type', 'book' ], + [ 'auto-update', 'yes' ], + [ 't', 'additional' ], + [ 't', 'tags' ], + [ 't', 'here' ], + [ 't', 'complex' ], + [ 't', 'metadata' ], + [ 't', 'all-types' ], + [ 'd', 'complex-metadata-document' ], + [ 'title', 'Complex Metadata Document' ], + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with only title and no sections +Parsed AsciiDoc: { + metadata: { + title: 'Document with No Sections', + version: 'Version', + summary: 'This document has no sections' + }, + content: '= Document with No Sections\n' + + ':summary: This document has no sections\n' + + '\n' + + 'This is just preamble content.', + sections: [] +} +Index event: { + documentTitle: 'Document with No Sections', + indexDTag: 'document-with-no-sections' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with No Sections' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has no sections' ], + [ 'd', 'document-with-no-sections' ], + [ 'title', 'Document with No Sections' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with special characters in title +Parsed AsciiDoc: { + metadata: { + title: 'Document with Special Characters: Test & More!', + version: 'Version', + summary: 'This document has special characters in the title' + }, + content: '= Document with Special Characters: Test & More!\n' + + ':summary: This document has special characters in the title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'Document with Special Characters: Test & More!', + indexDTag: 'document-with-special-characters-test-more' +} +Creating section 0: { + title: 'Section 1', + dTag: 'document-with-special-characters-test-more-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with Special Characters: Test & More!' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has special characters in the title' ], + [ 'd', 'document-with-special-characters-test-more' ], + [ 'title', 'Document with Special Characters: Test & More!' ], + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with very long title +Parsed AsciiDoc: { + metadata: { + title: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + version: 'Version', + summary: 'This document has a very long title' + }, + content: '= This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality\n' + + ':summary: This document has a very long title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + indexDTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' +} +Creating section 0: { + title: 'Section 1', + dTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ 'version', 'Version' ], + [ 'summary', 'This document has a very long title' ], + [ + 'd', + 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' + ], + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ✓ tests/unit/eventInput30040.test.ts (14 tests) 389ms +(node:1443840) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. +(Use `node --trace-warnings ...` to show where the warning was created) + ✓ tests/unit/mathProcessing.test.ts (18 tests) 16ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:44:30 + 42| + 43| expect(metadata.title).toBe("Test Document with Metadata"); + 44| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 45| expect(metadata.version).toBe("1.0"); + 46| expect(metadata.publicationDate).toBe("2024-01-15"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:318:32 + 316| // Should extract document-level metadata + 317| expect(metadata.title).toBe("Test Document"); + 318| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 319| expect(metadata.version).toBe("1.0"); + 320| expect(metadata.publishedBy).toBe("Alexandria Test"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯ + + + Test Files 1 failed | 6 passed (7) + Tests 2 failed | 116 passed (118) + Start at 13:16:23 + Duration 2.53s (transform 1.97s, setup 0ms, collect 3.66s, tests 710ms, environment 2ms, prepare 1.10s) + + FAIL Tests failed. Watching for file changes... + press h to show help, press q to quit +c RERUN src/lib/utils/asciidoc_metadata.ts + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure with Preamble > should build 30040 event set with preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document with Preamble', + authors: [ 'John Doe', 'Section Author' ], + version: '1.0', + publicationDate: '2024-01-15, Alexandria Test', + summary: 'This is a test document with preamble', + tags: [ 'test', 'preamble', 'asciidoc' ] + }, + content: '= Test Document with Preamble\n' + + 'John Doe \n' + + '1.0, 2024-01-15, Alexandria Test\n' + + ':summary: This is a test document with preamble\n' + + ':keywords: test, preamble, asciidoc\n' + + '\n' + + 'This is the preamble content that should be included.\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document with Preamble', + indexDTag: 'test-document-with-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-with-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-with-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document with Preamble' ], + [ 'author', 'John Doe' ], + [ 'author', 'Section Author' ], + [ 'version', '1.0' ], + [ 'published_on', '2024-01-15, Alexandria Test' ], + [ 'summary', 'This is a test document with preamble' ], + [ 't', 'test' ], + [ 't', 'preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-with-preamble' ], + [ 'title', 'Test Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure without Preamble > should build 30040 event set without preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document without Preamble', + authors: [ 'Section Author' ], + version: 'Version', + summary: 'This is a test document without preamble', + tags: [ 'test', 'no-preamble', 'asciidoc' ] + }, + content: '= Test Document without Preamble\n' + + ':summary: This is a test document without preamble\n' + + ':keywords: test, no-preamble, asciidoc\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document without Preamble', + indexDTag: 'test-document-without-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-without-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-without-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document without Preamble' ], + [ 'author', 'Section Author' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a test document without preamble' ], + [ 't', 'test' ], + [ 't', 'no-preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-without-preamble' ], + [ 'title', 'Test Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure with Preamble > should build 30040 event set with skeleton structure and preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document with Preamble', + version: 'Version', + summary: 'This is a skeleton document with preamble', + tags: [ 'skeleton', 'preamble', 'empty' ] + }, + content: '= Skeleton Document with Preamble\n' + + ':summary: This is a skeleton document with preamble\n' + + ':keywords: skeleton, preamble, empty\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document with Preamble', + indexDTag: 'skeleton-document-with-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-with-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-with-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-with-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document with preamble' ], + [ 't', 'skeleton' ], + [ 't', 'preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-with-preamble' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ❯ tests/unit/metadataExtraction.test.ts (16 tests | 2 failed) 202ms + × AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly 116ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract section metadata correctly 7ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract standalone author names and remove them from content 4ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should handle multiple standalone author names 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should not extract non-author lines as authors 4ms + ✓ AsciiDoc Metadata Extraction > parseAsciiDocWithMetadata should parse complete document 20ms + ✓ AsciiDoc Metadata Extraction > metadataToTags should convert metadata to Nostr tags 2ms + ✓ AsciiDoc Metadata Extraction > should handle index card format correctly 4ms + ✓ AsciiDoc Metadata Extraction > should handle empty content gracefully 4ms + ✓ AsciiDoc Metadata Extraction > should handle keywords as tags 4ms + ✓ AsciiDoc Metadata Extraction > should handle both tags and keywords 4ms + ✓ AsciiDoc Metadata Extraction > should handle tags only 3ms + ✓ AsciiDoc Metadata Extraction > should handle both summary and description 7ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle section-only content correctly 8ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle minimal document header (just title) correctly 1ms + × AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly 7ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure without Preamble > should build 30040 event set with skeleton structure without preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document without Preamble', + version: 'Version', + summary: 'This is a skeleton document without preamble', + tags: [ 'skeleton', 'no-preamble', 'empty' ] + }, + content: '= Skeleton Document without Preamble\n' + + ':summary: This is a skeleton document without preamble\n' + + ':keywords: skeleton, no-preamble, empty\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document without Preamble', + indexDTag: 'skeleton-document-without-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-without-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-without-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-without-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document without preamble' ], + [ 't', 'skeleton' ], + [ 't', 'no-preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-without-preamble' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card format +Parsed AsciiDoc: { + metadata: { title: 'Test Index Card', version: 'Version' }, + content: '= Test Index Card\nindex card', + sections: [] +} +Creating index card format (no sections) + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card with metadata +Parsed AsciiDoc: { + metadata: { + title: 'Test Index Card with Metadata', + version: 'Version', + summary: 'This is an index card with metadata', + tags: [ 'index', 'card', 'metadata' ] + }, + content: '= Test Index Card with Metadata\n' + + ':summary: This is an index card with metadata\n' + + ':keywords: index, card, metadata\n' + + 'index card', + sections: [] +} +Index event: { + documentTitle: 'Test Index Card with Metadata', + indexDTag: 'test-index-card-with-metadata' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'index-card' ], + [ 'title', 'Test Index Card with Metadata' ], + [ 'version', 'Version' ], + [ 'summary', 'This is an index card with metadata' ], + [ 't', 'index' ], + [ 't', 'card' ], + [ 't', 'metadata' ], + [ 'd', 'test-index-card-with-metadata' ], + [ 'title', 'Test Index Card with Metadata' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Complex Metadata Structures > should handle complex metadata with all attribute types +Parsed AsciiDoc: { + metadata: { + title: 'Complex Metadata Document', + authors: [ + 'Jane Smith', + 'Override Author', + 'Third Author', + 'Section Author', + 'Section Co-Author' + ], + version: '2.0', + publicationDate: '2024-03-01', + summary: 'This is a complex document with all metadata types Alternative description field', + publishedBy: 'Alexandria Complex', + type: 'book', + coverImage: 'https://example.com/cover.jpg', + isbn: '978-0-123456-78-9', + source: 'https://github.com/alexandria/complex', + autoUpdate: 'yes', + tags: [ + 'additional', + 'tags', + 'here', + 'complex', + 'metadata', + 'all-types' + ] + }, + content: '= Complex Metadata Document\n' + + 'Jane Smith \n' + + '2.0, 2024-02-20, Alexandria Complex\n' + + ':summary: This is a complex document with all metadata types\n' + + ':description: Alternative description field\n' + + ':keywords: complex, metadata, all-types\n' + + ':tags: additional, tags, here\n' + + ':author: Override Author\n' + + ':author: Third Author\n' + + ':version: 3.0\n' + + ':published_on: 2024-03-01\n' + + ':published_by: Alexandria Complex\n' + + ':type: book\n' + + ':image: https://example.com/cover.jpg\n' + + ':isbn: 978-0-123456-78-9\n' + + ':source: https://github.com/alexandria/complex\n' + + ':auto-update: yes\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Section with Complex Metadata\n' + + ':author: Section Author\n' + + ':author: Section Co-Author\n' + + ':summary: This section has complex metadata\n' + + ':description: Alternative description for section\n' + + ':keywords: section, complex, metadata\n' + + ':tags: section, tags\n' + + ':type: chapter\n' + + ':image: https://example.com/section-image.jpg\n' + + '\n' + + 'This is the section content.', + sections: [ + { + metadata: [Object], + content: 'This is the section content.', + title: 'Section with Complex Metadata' + } + ] +} +Index event: { + documentTitle: 'Complex Metadata Document', + indexDTag: 'complex-metadata-document' +} +Creating section 0: { + title: 'Section with Complex Metadata', + dTag: 'complex-metadata-document-section-with-complex-metadata', + content: 'This is the section content.', + metadata: { + title: 'Section with Complex Metadata', + authors: [ 'Section Author', 'Section Co-Author' ], + summary: 'This section has complex metadata Alternative description for section', + type: 'chapter', + coverImage: 'https://example.com/section-image.jpg', + tags: [ 'section', 'tags', 'complex', 'metadata' ] + } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'complex' ], + [ 'title', 'Complex Metadata Document' ], + [ 'author', 'Jane Smith' ], + [ 'author', 'Override Author' ], + [ 'author', 'Third Author' ], + [ 'author', 'Section Author' ], + [ 'author', 'Section Co-Author' ], + [ 'version', '2.0' ], + [ 'published_on', '2024-03-01' ], + [ 'published_by', 'Alexandria Complex' ], + [ + 'summary', + 'This is a complex document with all metadata types Alternative description field' + ], + [ 'image', 'https://example.com/cover.jpg' ], + [ 'i', '978-0-123456-78-9' ], + [ 'source', 'https://github.com/alexandria/complex' ], + [ 'type', 'book' ], + [ 'auto-update', 'yes' ], + [ 't', 'additional' ], + [ 't', 'tags' ], + [ 't', 'here' ], + [ 't', 'complex' ], + [ 't', 'metadata' ], + [ 't', 'all-types' ], + [ 'd', 'complex-metadata-document' ], + [ 'title', 'Complex Metadata Document' ], + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with only title and no sections +Parsed AsciiDoc: { + metadata: { + title: 'Document with No Sections', + version: 'Version', + summary: 'This document has no sections' + }, + content: '= Document with No Sections\n' + + ':summary: This document has no sections\n' + + '\n' + + 'This is just preamble content.', + sections: [] +} +Index event: { + documentTitle: 'Document with No Sections', + indexDTag: 'document-with-no-sections' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with No Sections' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has no sections' ], + [ 'd', 'document-with-no-sections' ], + [ 'title', 'Document with No Sections' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with special characters in title +Parsed AsciiDoc: { + metadata: { + title: 'Document with Special Characters: Test & More!', + version: 'Version', + summary: 'This document has special characters in the title' + }, + content: '= Document with Special Characters: Test & More!\n' + + ':summary: This document has special characters in the title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'Document with Special Characters: Test & More!', + indexDTag: 'document-with-special-characters-test-more' +} +Creating section 0: { + title: 'Section 1', + dTag: 'document-with-special-characters-test-more-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with Special Characters: Test & More!' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has special characters in the title' ], + [ 'd', 'document-with-special-characters-test-more' ], + [ 'title', 'Document with Special Characters: Test & More!' ], + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with very long title +Parsed AsciiDoc: { + metadata: { + title: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + version: 'Version', + summary: 'This document has a very long title' + }, + content: '= This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality\n' + + ':summary: This document has a very long title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + indexDTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' +} +Creating section 0: { + title: 'Section 1', + dTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ 'version', 'Version' ], + [ 'summary', 'This document has a very long title' ], + [ + 'd', + 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' + ], + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ✓ tests/unit/eventInput30040.test.ts (14 tests) 333ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:44:30 + 42| + 43| expect(metadata.title).toBe("Test Document with Metadata"); + 44| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 45| expect(metadata.version).toBe("1.0"); + 46| expect(metadata.publicationDate).toBe("2024-01-15"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:318:32 + 316| // Should extract document-level metadata + 317| expect(metadata.title).toBe("Test Document"); + 318| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 319| expect(metadata.version).toBe("1.0"); + 320| expect(metadata.publishedBy).toBe("Alexandria Test"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯ + + + Test Files 1 failed | 1 passed (2) + Tests 2 failed | 28 passed (30) + Start at 13:18:57 + Duration 1.00s + + FAIL Tests failed. Watching for file changes... + press h to show help, press q to quit +c RERUN src/lib/utils/asciidoc_metadata.ts + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure with Preamble > should build 30040 event set with preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document with Preamble', + authors: [ 'John Doe', 'Section Author' ], + version: '1.0', + publicationDate: '2024-01-15, Alexandria Test', + summary: 'This is a test document with preamble', + tags: [ 'test', 'preamble', 'asciidoc' ] + }, + content: '= Test Document with Preamble\n' + + 'John Doe \n' + + '1.0, 2024-01-15, Alexandria Test\n' + + ':summary: This is a test document with preamble\n' + + ':keywords: test, preamble, asciidoc\n' + + '\n' + + 'This is the preamble content that should be included.\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document with Preamble', + indexDTag: 'test-document-with-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-with-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-with-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document with Preamble' ], + [ 'author', 'John Doe' ], + [ 'author', 'Section Author' ], + [ 'version', '1.0' ], + [ 'published_on', '2024-01-15, Alexandria Test' ], + [ 'summary', 'This is a test document with preamble' ], + [ 't', 'test' ], + [ 't', 'preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-with-preamble' ], + [ 'title', 'Test Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure without Preamble > should build 30040 event set without preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document without Preamble', + authors: [ 'Section Author' ], + version: 'Version', + summary: 'This is a test document without preamble', + tags: [ 'test', 'no-preamble', 'asciidoc' ] + }, + content: '= Test Document without Preamble\n' + + ':summary: This is a test document without preamble\n' + + ':keywords: test, no-preamble, asciidoc\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document without Preamble', + indexDTag: 'test-document-without-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-without-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-without-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document without Preamble' ], + [ 'author', 'Section Author' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a test document without preamble' ], + [ 't', 'test' ], + [ 't', 'no-preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-without-preamble' ], + [ 'title', 'Test Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ❯ tests/unit/metadataExtraction.test.ts (16 tests | 2 failed) 228ms + × AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly 129ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract section metadata correctly 8ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract standalone author names and remove them from content 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should handle multiple standalone author names 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should not extract non-author lines as authors 4ms + ✓ AsciiDoc Metadata Extraction > parseAsciiDocWithMetadata should parse complete document 21ms + ✓ AsciiDoc Metadata Extraction > metadataToTags should convert metadata to Nostr tags 2ms + ✓ AsciiDoc Metadata Extraction > should handle index card format correctly 5ms + ✓ AsciiDoc Metadata Extraction > should handle empty content gracefully 4ms + ✓ AsciiDoc Metadata Extraction > should handle keywords as tags 4ms + ✓ AsciiDoc Metadata Extraction > should handle both tags and keywords 5ms + ✓ AsciiDoc Metadata Extraction > should handle tags only 4ms + ✓ AsciiDoc Metadata Extraction > should handle both summary and description 8ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle section-only content correctly 10ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle minimal document header (just title) correctly 1ms + × AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly 9ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure with Preamble > should build 30040 event set with skeleton structure and preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document with Preamble', + version: 'Version', + summary: 'This is a skeleton document with preamble', + tags: [ 'skeleton', 'preamble', 'empty' ] + }, + content: '= Skeleton Document with Preamble\n' + + ':summary: This is a skeleton document with preamble\n' + + ':keywords: skeleton, preamble, empty\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document with Preamble', + indexDTag: 'skeleton-document-with-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-with-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-with-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-with-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document with preamble' ], + [ 't', 'skeleton' ], + [ 't', 'preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-with-preamble' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure without Preamble > should build 30040 event set with skeleton structure without preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document without Preamble', + version: 'Version', + summary: 'This is a skeleton document without preamble', + tags: [ 'skeleton', 'no-preamble', 'empty' ] + }, + content: '= Skeleton Document without Preamble\n' + + ':summary: This is a skeleton document without preamble\n' + + ':keywords: skeleton, no-preamble, empty\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document without Preamble', + indexDTag: 'skeleton-document-without-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-without-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-without-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-without-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document without preamble' ], + [ 't', 'skeleton' ], + [ 't', 'no-preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-without-preamble' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card format +Parsed AsciiDoc: { + metadata: { title: 'Test Index Card', version: 'Version' }, + content: '= Test Index Card\nindex card', + sections: [] +} +Creating index card format (no sections) + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card with metadata +Parsed AsciiDoc: { + metadata: { + title: 'Test Index Card with Metadata', + version: 'Version', + summary: 'This is an index card with metadata', + tags: [ 'index', 'card', 'metadata' ] + }, + content: '= Test Index Card with Metadata\n' + + ':summary: This is an index card with metadata\n' + + ':keywords: index, card, metadata\n' + + 'index card', + sections: [] +} +Index event: { + documentTitle: 'Test Index Card with Metadata', + indexDTag: 'test-index-card-with-metadata' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'index-card' ], + [ 'title', 'Test Index Card with Metadata' ], + [ 'version', 'Version' ], + [ 'summary', 'This is an index card with metadata' ], + [ 't', 'index' ], + [ 't', 'card' ], + [ 't', 'metadata' ], + [ 'd', 'test-index-card-with-metadata' ], + [ 'title', 'Test Index Card with Metadata' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Complex Metadata Structures > should handle complex metadata with all attribute types +Parsed AsciiDoc: { + metadata: { + title: 'Complex Metadata Document', + authors: [ + 'Jane Smith', + 'Override Author', + 'Third Author', + 'Section Author', + 'Section Co-Author' + ], + version: '2.0', + publicationDate: '2024-03-01', + summary: 'This is a complex document with all metadata types Alternative description field', + publishedBy: 'Alexandria Complex', + type: 'book', + coverImage: 'https://example.com/cover.jpg', + isbn: '978-0-123456-78-9', + source: 'https://github.com/alexandria/complex', + autoUpdate: 'yes', + tags: [ + 'additional', + 'tags', + 'here', + 'complex', + 'metadata', + 'all-types' + ] + }, + content: '= Complex Metadata Document\n' + + 'Jane Smith \n' + + '2.0, 2024-02-20, Alexandria Complex\n' + + ':summary: This is a complex document with all metadata types\n' + + ':description: Alternative description field\n' + + ':keywords: complex, metadata, all-types\n' + + ':tags: additional, tags, here\n' + + ':author: Override Author\n' + + ':author: Third Author\n' + + ':version: 3.0\n' + + ':published_on: 2024-03-01\n' + + ':published_by: Alexandria Complex\n' + + ':type: book\n' + + ':image: https://example.com/cover.jpg\n' + + ':isbn: 978-0-123456-78-9\n' + + ':source: https://github.com/alexandria/complex\n' + + ':auto-update: yes\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Section with Complex Metadata\n' + + ':author: Section Author\n' + + ':author: Section Co-Author\n' + + ':summary: This section has complex metadata\n' + + ':description: Alternative description for section\n' + + ':keywords: section, complex, metadata\n' + + ':tags: section, tags\n' + + ':type: chapter\n' + + ':image: https://example.com/section-image.jpg\n' + + '\n' + + 'This is the section content.', + sections: [ + { + metadata: [Object], + content: 'This is the section content.', + title: 'Section with Complex Metadata' + } + ] +} +Index event: { + documentTitle: 'Complex Metadata Document', + indexDTag: 'complex-metadata-document' +} +Creating section 0: { + title: 'Section with Complex Metadata', + dTag: 'complex-metadata-document-section-with-complex-metadata', + content: 'This is the section content.', + metadata: { + title: 'Section with Complex Metadata', + authors: [ 'Section Author', 'Section Co-Author' ], + summary: 'This section has complex metadata Alternative description for section', + type: 'chapter', + coverImage: 'https://example.com/section-image.jpg', + tags: [ 'section', 'tags', 'complex', 'metadata' ] + } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'complex' ], + [ 'title', 'Complex Metadata Document' ], + [ 'author', 'Jane Smith' ], + [ 'author', 'Override Author' ], + [ 'author', 'Third Author' ], + [ 'author', 'Section Author' ], + [ 'author', 'Section Co-Author' ], + [ 'version', '2.0' ], + [ 'published_on', '2024-03-01' ], + [ 'published_by', 'Alexandria Complex' ], + [ + 'summary', + 'This is a complex document with all metadata types Alternative description field' + ], + [ 'image', 'https://example.com/cover.jpg' ], + [ 'i', '978-0-123456-78-9' ], + [ 'source', 'https://github.com/alexandria/complex' ], + [ 'type', 'book' ], + [ 'auto-update', 'yes' ], + [ 't', 'additional' ], + [ 't', 'tags' ], + [ 't', 'here' ], + [ 't', 'complex' ], + [ 't', 'metadata' ], + [ 't', 'all-types' ], + [ 'd', 'complex-metadata-document' ], + [ 'title', 'Complex Metadata Document' ], + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with only title and no sections +Parsed AsciiDoc: { + metadata: { + title: 'Document with No Sections', + version: 'Version', + summary: 'This document has no sections' + }, + content: '= Document with No Sections\n' + + ':summary: This document has no sections\n' + + '\n' + + 'This is just preamble content.', + sections: [] +} +Index event: { + documentTitle: 'Document with No Sections', + indexDTag: 'document-with-no-sections' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with No Sections' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has no sections' ], + [ 'd', 'document-with-no-sections' ], + [ 'title', 'Document with No Sections' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with special characters in title +Parsed AsciiDoc: { + metadata: { + title: 'Document with Special Characters: Test & More!', + version: 'Version', + summary: 'This document has special characters in the title' + }, + content: '= Document with Special Characters: Test & More!\n' + + ':summary: This document has special characters in the title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'Document with Special Characters: Test & More!', + indexDTag: 'document-with-special-characters-test-more' +} +Creating section 0: { + title: 'Section 1', + dTag: 'document-with-special-characters-test-more-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with Special Characters: Test & More!' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has special characters in the title' ], + [ 'd', 'document-with-special-characters-test-more' ], + [ 'title', 'Document with Special Characters: Test & More!' ], + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with very long title +Parsed AsciiDoc: { + metadata: { + title: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + version: 'Version', + summary: 'This document has a very long title' + }, + content: '= This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality\n' + + ':summary: This document has a very long title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + indexDTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' +} +Creating section 0: { + title: 'Section 1', + dTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ 'version', 'Version' ], + [ 'summary', 'This document has a very long title' ], + [ + 'd', + 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' + ], + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ✓ tests/unit/eventInput30040.test.ts (14 tests) 424ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:44:30 + 42| + 43| expect(metadata.title).toBe("Test Document with Metadata"); + 44| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 45| expect(metadata.version).toBe("1.0"); + 46| expect(metadata.publicationDate).toBe("2024-01-15"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:318:32 + 316| // Should extract document-level metadata + 317| expect(metadata.title).toBe("Test Document"); + 318| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 319| expect(metadata.version).toBe("1.0"); + 320| expect(metadata.publishedBy).toBe("Alexandria Test"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯ + + + Test Files 1 failed | 1 passed (2) + Tests 2 failed | 28 passed (30) + Start at 13:19:15 + Duration 1.04s + + FAIL Tests failed. Watching for file changes... + press h to show help, press q to quit +c RERUN src/lib/utils/asciidoc_metadata.ts + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure with Preamble > should build 30040 event set with preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document with Preamble', + authors: [ 'John Doe', 'Section Author' ], + version: '1.0', + publicationDate: '2024-01-15, Alexandria Test', + summary: 'This is a test document with preamble', + tags: [ 'test', 'preamble', 'asciidoc' ] + }, + content: '= Test Document with Preamble\n' + + 'John Doe \n' + + '1.0, 2024-01-15, Alexandria Test\n' + + ':summary: This is a test document with preamble\n' + + ':keywords: test, preamble, asciidoc\n' + + '\n' + + 'This is the preamble content that should be included.\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document with Preamble', + indexDTag: 'test-document-with-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-with-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-with-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document with Preamble' ], + [ 'author', 'John Doe' ], + [ 'author', 'Section Author' ], + [ 'version', '1.0' ], + [ 'published_on', '2024-01-15, Alexandria Test' ], + [ 'summary', 'This is a test document with preamble' ], + [ 't', 'test' ], + [ 't', 'preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-with-preamble' ], + [ 'title', 'Test Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure without Preamble > should build 30040 event set without preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document without Preamble', + authors: [ 'Section Author' ], + version: 'Version', + summary: 'This is a test document without preamble', + tags: [ 'test', 'no-preamble', 'asciidoc' ] + }, + content: '= Test Document without Preamble\n' + + ':summary: This is a test document without preamble\n' + + ':keywords: test, no-preamble, asciidoc\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document without Preamble', + indexDTag: 'test-document-without-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-without-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-without-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document without Preamble' ], + [ 'author', 'Section Author' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a test document without preamble' ], + [ 't', 'test' ], + [ 't', 'no-preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-without-preamble' ], + [ 'title', 'Test Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure with Preamble > should build 30040 event set with skeleton structure and preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document with Preamble', + version: 'Version', + summary: 'This is a skeleton document with preamble', + tags: [ 'skeleton', 'preamble', 'empty' ] + }, + content: '= Skeleton Document with Preamble\n' + + ':summary: This is a skeleton document with preamble\n' + + ':keywords: skeleton, preamble, empty\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document with Preamble', + indexDTag: 'skeleton-document-with-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-with-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-with-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-with-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document with preamble' ], + [ 't', 'skeleton' ], + [ 't', 'preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-with-preamble' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure without Preamble > should build 30040 event set with skeleton structure without preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document without Preamble', + version: 'Version', + summary: 'This is a skeleton document without preamble', + tags: [ 'skeleton', 'no-preamble', 'empty' ] + }, + content: '= Skeleton Document without Preamble\n' + + ':summary: This is a skeleton document without preamble\n' + + ':keywords: skeleton, no-preamble, empty\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document without Preamble', + indexDTag: 'skeleton-document-without-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-without-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-without-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-without-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document without preamble' ], + [ 't', 'skeleton' ], + [ 't', 'no-preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-without-preamble' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ❯ tests/unit/metadataExtraction.test.ts (16 tests | 2 failed) 246ms + × AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly 154ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract section metadata correctly 8ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract standalone author names and remove them from content 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should handle multiple standalone author names 5ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should not extract non-author lines as authors 4ms + ✓ AsciiDoc Metadata Extraction > parseAsciiDocWithMetadata should parse complete document 21ms + ✓ AsciiDoc Metadata Extraction > metadataToTags should convert metadata to Nostr tags 2ms + ✓ AsciiDoc Metadata Extraction > should handle index card format correctly 4ms + ✓ AsciiDoc Metadata Extraction > should handle empty content gracefully 4ms + ✓ AsciiDoc Metadata Extraction > should handle keywords as tags 5ms + ✓ AsciiDoc Metadata Extraction > should handle both tags and keywords 4ms + ✓ AsciiDoc Metadata Extraction > should handle tags only 4ms + ✓ AsciiDoc Metadata Extraction > should handle both summary and description 7ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle section-only content correctly 8ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle minimal document header (just title) correctly 1ms + × AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly 7ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card format +Parsed AsciiDoc: { + metadata: { title: 'Test Index Card', version: 'Version' }, + content: '= Test Index Card\nindex card', + sections: [] +} +Creating index card format (no sections) + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card with metadata +Parsed AsciiDoc: { + metadata: { + title: 'Test Index Card with Metadata', + version: 'Version', + summary: 'This is an index card with metadata', + tags: [ 'index', 'card', 'metadata' ] + }, + content: '= Test Index Card with Metadata\n' + + ':summary: This is an index card with metadata\n' + + ':keywords: index, card, metadata\n' + + 'index card', + sections: [] +} +Index event: { + documentTitle: 'Test Index Card with Metadata', + indexDTag: 'test-index-card-with-metadata' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'index-card' ], + [ 'title', 'Test Index Card with Metadata' ], + [ 'version', 'Version' ], + [ 'summary', 'This is an index card with metadata' ], + [ 't', 'index' ], + [ 't', 'card' ], + [ 't', 'metadata' ], + [ 'd', 'test-index-card-with-metadata' ], + [ 'title', 'Test Index Card with Metadata' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Complex Metadata Structures > should handle complex metadata with all attribute types +Parsed AsciiDoc: { + metadata: { + title: 'Complex Metadata Document', + authors: [ + 'Jane Smith', + 'Override Author', + 'Third Author', + 'Section Author', + 'Section Co-Author' + ], + version: '2.0', + publicationDate: '2024-03-01', + summary: 'This is a complex document with all metadata types Alternative description field', + publishedBy: 'Alexandria Complex', + type: 'book', + coverImage: 'https://example.com/cover.jpg', + isbn: '978-0-123456-78-9', + source: 'https://github.com/alexandria/complex', + autoUpdate: 'yes', + tags: [ + 'additional', + 'tags', + 'here', + 'complex', + 'metadata', + 'all-types' + ] + }, + content: '= Complex Metadata Document\n' + + 'Jane Smith \n' + + '2.0, 2024-02-20, Alexandria Complex\n' + + ':summary: This is a complex document with all metadata types\n' + + ':description: Alternative description field\n' + + ':keywords: complex, metadata, all-types\n' + + ':tags: additional, tags, here\n' + + ':author: Override Author\n' + + ':author: Third Author\n' + + ':version: 3.0\n' + + ':published_on: 2024-03-01\n' + + ':published_by: Alexandria Complex\n' + + ':type: book\n' + + ':image: https://example.com/cover.jpg\n' + + ':isbn: 978-0-123456-78-9\n' + + ':source: https://github.com/alexandria/complex\n' + + ':auto-update: yes\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Section with Complex Metadata\n' + + ':author: Section Author\n' + + ':author: Section Co-Author\n' + + ':summary: This section has complex metadata\n' + + ':description: Alternative description for section\n' + + ':keywords: section, complex, metadata\n' + + ':tags: section, tags\n' + + ':type: chapter\n' + + ':image: https://example.com/section-image.jpg\n' + + '\n' + + 'This is the section content.', + sections: [ + { + metadata: [Object], + content: 'This is the section content.', + title: 'Section with Complex Metadata' + } + ] +} +Index event: { + documentTitle: 'Complex Metadata Document', + indexDTag: 'complex-metadata-document' +} +Creating section 0: { + title: 'Section with Complex Metadata', + dTag: 'complex-metadata-document-section-with-complex-metadata', + content: 'This is the section content.', + metadata: { + title: 'Section with Complex Metadata', + authors: [ 'Section Author', 'Section Co-Author' ], + summary: 'This section has complex metadata Alternative description for section', + type: 'chapter', + coverImage: 'https://example.com/section-image.jpg', + tags: [ 'section', 'tags', 'complex', 'metadata' ] + } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'complex' ], + [ 'title', 'Complex Metadata Document' ], + [ 'author', 'Jane Smith' ], + [ 'author', 'Override Author' ], + [ 'author', 'Third Author' ], + [ 'author', 'Section Author' ], + [ 'author', 'Section Co-Author' ], + [ 'version', '2.0' ], + [ 'published_on', '2024-03-01' ], + [ 'published_by', 'Alexandria Complex' ], + [ + 'summary', + 'This is a complex document with all metadata types Alternative description field' + ], + [ 'image', 'https://example.com/cover.jpg' ], + [ 'i', '978-0-123456-78-9' ], + [ 'source', 'https://github.com/alexandria/complex' ], + [ 'type', 'book' ], + [ 'auto-update', 'yes' ], + [ 't', 'additional' ], + [ 't', 'tags' ], + [ 't', 'here' ], + [ 't', 'complex' ], + [ 't', 'metadata' ], + [ 't', 'all-types' ], + [ 'd', 'complex-metadata-document' ], + [ 'title', 'Complex Metadata Document' ], + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with only title and no sections +Parsed AsciiDoc: { + metadata: { + title: 'Document with No Sections', + version: 'Version', + summary: 'This document has no sections' + }, + content: '= Document with No Sections\n' + + ':summary: This document has no sections\n' + + '\n' + + 'This is just preamble content.', + sections: [] +} +Index event: { + documentTitle: 'Document with No Sections', + indexDTag: 'document-with-no-sections' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with No Sections' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has no sections' ], + [ 'd', 'document-with-no-sections' ], + [ 'title', 'Document with No Sections' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with special characters in title +Parsed AsciiDoc: { + metadata: { + title: 'Document with Special Characters: Test & More!', + version: 'Version', + summary: 'This document has special characters in the title' + }, + content: '= Document with Special Characters: Test & More!\n' + + ':summary: This document has special characters in the title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'Document with Special Characters: Test & More!', + indexDTag: 'document-with-special-characters-test-more' +} +Creating section 0: { + title: 'Section 1', + dTag: 'document-with-special-characters-test-more-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with Special Characters: Test & More!' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has special characters in the title' ], + [ 'd', 'document-with-special-characters-test-more' ], + [ 'title', 'Document with Special Characters: Test & More!' ], + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with very long title +Parsed AsciiDoc: { + metadata: { + title: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + version: 'Version', + summary: 'This document has a very long title' + }, + content: '= This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality\n' + + ':summary: This document has a very long title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + indexDTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' +} +Creating section 0: { + title: 'Section 1', + dTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ 'version', 'Version' ], + [ 'summary', 'This document has a very long title' ], + [ + 'd', + 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' + ], + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ✓ tests/unit/eventInput30040.test.ts (14 tests) 374ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:44:30 + 42| + 43| expect(metadata.title).toBe("Test Document with Metadata"); + 44| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 45| expect(metadata.version).toBe("1.0"); + 46| expect(metadata.publicationDate).toBe("2024-01-15"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:318:32 + 316| // Should extract document-level metadata + 317| expect(metadata.title).toBe("Test Document"); + 318| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 319| expect(metadata.version).toBe("1.0"); + 320| expect(metadata.publishedBy).toBe("Alexandria Test"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯ + + + Test Files 1 failed | 1 passed (2) + Tests 2 failed | 28 passed (30) + Start at 13:20:56 + Duration 1.17s + + FAIL Tests failed. Watching for file changes... + press h to show help, press q to quit +c RERUN src/lib/utils/asciidoc_metadata.ts + + ❯ tests/unit/metadataExtraction.test.ts (16 tests | 2 failed) 270ms + × AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly 158ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract section metadata correctly 8ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should extract standalone author names and remove them from content 6ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should handle multiple standalone author names 6ms + ✓ AsciiDoc Metadata Extraction > extractSectionMetadata should not extract non-author lines as authors 6ms + ✓ AsciiDoc Metadata Extraction > parseAsciiDocWithMetadata should parse complete document 26ms + ✓ AsciiDoc Metadata Extraction > metadataToTags should convert metadata to Nostr tags 2ms + ✓ AsciiDoc Metadata Extraction > should handle index card format correctly 5ms + ✓ AsciiDoc Metadata Extraction > should handle empty content gracefully 5ms + ✓ AsciiDoc Metadata Extraction > should handle keywords as tags 5ms + ✓ AsciiDoc Metadata Extraction > should handle both tags and keywords 4ms + ✓ AsciiDoc Metadata Extraction > should handle tags only 4ms + ✓ AsciiDoc Metadata Extraction > should handle both summary and description 9ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle section-only content correctly 9ms + ✓ AsciiDoc Metadata Extraction > Smart metadata extraction > should handle minimal document header (just title) correctly 1ms + × AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly 11ms + → expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure with Preamble > should build 30040 event set with preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document with Preamble', + authors: [ 'John Doe', 'Section Author' ], + version: '1.0', + publicationDate: '2024-01-15, Alexandria Test', + summary: 'This is a test document with preamble', + tags: [ 'test', 'preamble', 'asciidoc' ] + }, + content: '= Test Document with Preamble\n' + + 'John Doe \n' + + '1.0, 2024-01-15, Alexandria Test\n' + + ':summary: This is a test document with preamble\n' + + ':keywords: test, preamble, asciidoc\n' + + '\n' + + 'This is the preamble content that should be included.\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document with Preamble', + indexDTag: 'test-document-with-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-with-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-with-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document with Preamble' ], + [ 'author', 'John Doe' ], + [ 'author', 'Section Author' ], + [ 'version', '1.0' ], + [ 'published_on', '2024-01-15, Alexandria Test' ], + [ 'summary', 'This is a test document with preamble' ], + [ 't', 'test' ], + [ 't', 'preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-with-preamble' ], + [ 'title', 'Test Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-with-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Normal Structure without Preamble > should build 30040 event set without preamble content +Parsed AsciiDoc: { + metadata: { + title: 'Test Document without Preamble', + authors: [ 'Section Author' ], + version: 'Version', + summary: 'This is a test document without preamble', + tags: [ 'test', 'no-preamble', 'asciidoc' ] + }, + content: '= Test Document without Preamble\n' + + ':summary: This is a test document without preamble\n' + + ':keywords: test, no-preamble, asciidoc\n' + + '\n' + + '== First Section\n' + + ':author: Section Author\n' + + ':summary: This is the first section\n' + + '\n' + + 'This is the content of the first section.\n' + + '\n' + + '== Second Section\n' + + ':summary: This is the second section\n' + + '\n' + + 'This is the content of the second section.', + sections: [ + { + metadata: [Object], + content: 'This is the content of the first section.', + title: 'First Section' + }, + { + metadata: [Object], + content: 'This is the content of the second section.', + title: 'Second Section' + } + ] +} +Index event: { + documentTitle: 'Test Document without Preamble', + indexDTag: 'test-document-without-preamble' +} +Creating section 0: { + title: 'First Section', + dTag: 'test-document-without-preamble-first-section', + content: 'This is the content of the first section.', + metadata: { + title: 'First Section', + authors: [ 'Section Author' ], + summary: 'This is the first section' + } +} +Creating section 1: { + title: 'Second Section', + dTag: 'test-document-without-preamble-second-section', + content: 'This is the content of the second section.', + metadata: { title: 'Second Section', summary: 'This is the second section' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'article' ], + [ 'title', 'Test Document without Preamble' ], + [ 'author', 'Section Author' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a test document without preamble' ], + [ 't', 'test' ], + [ 't', 'no-preamble' ], + [ 't', 'asciidoc' ], + [ 'd', 'test-document-without-preamble' ], + [ 'title', 'Test Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-first-section' + ], + [ + 'a', + '30041:test-pubkey:test-document-without-preamble-second-section' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure with Preamble > should build 30040 event set with skeleton structure and preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document with Preamble', + version: 'Version', + summary: 'This is a skeleton document with preamble', + tags: [ 'skeleton', 'preamble', 'empty' ] + }, + content: '= Skeleton Document with Preamble\n' + + ':summary: This is a skeleton document with preamble\n' + + ':keywords: skeleton, preamble, empty\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document with Preamble', + indexDTag: 'skeleton-document-with-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-with-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-with-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-with-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document with preamble' ], + [ 't', 'skeleton' ], + [ 't', 'preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-with-preamble' ], + [ 'title', 'Skeleton Document with Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-with-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Skeleton Structure without Preamble > should build 30040 event set with skeleton structure without preamble +Parsed AsciiDoc: { + metadata: { + title: 'Skeleton Document without Preamble', + version: 'Version', + summary: 'This is a skeleton document without preamble', + tags: [ 'skeleton', 'no-preamble', 'empty' ] + }, + content: '= Skeleton Document without Preamble\n' + + ':summary: This is a skeleton document without preamble\n' + + ':keywords: skeleton, no-preamble, empty\n' + + '\n' + + '== Empty Section 1\n' + + '\n' + + '== Empty Section 2\n' + + '\n' + + '== Empty Section 3', + sections: [ + { metadata: [Object], content: '', title: 'Empty Section 1' }, + { metadata: [Object], content: '', title: 'Empty Section 2' }, + { metadata: [Object], content: '', title: 'Empty Section 3' } + ] +} +Index event: { + documentTitle: 'Skeleton Document without Preamble', + indexDTag: 'skeleton-document-without-preamble' +} +Creating section 0: { + title: 'Empty Section 1', + dTag: 'skeleton-document-without-preamble-empty-section-1', + content: '', + metadata: { title: 'Empty Section 1' } +} +Creating section 1: { + title: 'Empty Section 2', + dTag: 'skeleton-document-without-preamble-empty-section-2', + content: '', + metadata: { title: 'Empty Section 2' } +} +Creating section 2: { + title: 'Empty Section 3', + dTag: 'skeleton-document-without-preamble-empty-section-3', + content: '', + metadata: { title: 'Empty Section 3' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'skeleton' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ 'version', 'Version' ], + [ 'summary', 'This is a skeleton document without preamble' ], + [ 't', 'skeleton' ], + [ 't', 'no-preamble' ], + [ 't', 'empty' ], + [ 'd', 'skeleton-document-without-preamble' ], + [ 'title', 'Skeleton Document without Preamble' ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-1' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-2' + ], + [ + 'a', + '30041:test-pubkey:skeleton-document-without-preamble-empty-section-3' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card format +Parsed AsciiDoc: { + metadata: { title: 'Test Index Card', version: 'Version' }, + content: '= Test Index Card\nindex card', + sections: [] +} +Creating index card format (no sections) + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Index Card Format > should build 30040 event set for index card with metadata +Parsed AsciiDoc: { + metadata: { + title: 'Test Index Card with Metadata', + version: 'Version', + summary: 'This is an index card with metadata', + tags: [ 'index', 'card', 'metadata' ] + }, + content: '= Test Index Card with Metadata\n' + + ':summary: This is an index card with metadata\n' + + ':keywords: index, card, metadata\n' + + 'index card', + sections: [] +} +Index event: { + documentTitle: 'Test Index Card with Metadata', + indexDTag: 'test-index-card-with-metadata' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'index-card' ], + [ 'title', 'Test Index Card with Metadata' ], + [ 'version', 'Version' ], + [ 'summary', 'This is an index card with metadata' ], + [ 't', 'index' ], + [ 't', 'card' ], + [ 't', 'metadata' ], + [ 'd', 'test-index-card-with-metadata' ], + [ 'title', 'Test Index Card with Metadata' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Complex Metadata Structures > should handle complex metadata with all attribute types +Parsed AsciiDoc: { + metadata: { + title: 'Complex Metadata Document', + authors: [ + 'Jane Smith', + 'Override Author', + 'Third Author', + 'Section Author', + 'Section Co-Author' + ], + version: '2.0', + publicationDate: '2024-03-01', + summary: 'This is a complex document with all metadata types Alternative description field', + publishedBy: 'Alexandria Complex', + type: 'book', + coverImage: 'https://example.com/cover.jpg', + isbn: '978-0-123456-78-9', + source: 'https://github.com/alexandria/complex', + autoUpdate: 'yes', + tags: [ + 'additional', + 'tags', + 'here', + 'complex', + 'metadata', + 'all-types' + ] + }, + content: '= Complex Metadata Document\n' + + 'Jane Smith \n' + + '2.0, 2024-02-20, Alexandria Complex\n' + + ':summary: This is a complex document with all metadata types\n' + + ':description: Alternative description field\n' + + ':keywords: complex, metadata, all-types\n' + + ':tags: additional, tags, here\n' + + ':author: Override Author\n' + + ':author: Third Author\n' + + ':version: 3.0\n' + + ':published_on: 2024-03-01\n' + + ':published_by: Alexandria Complex\n' + + ':type: book\n' + + ':image: https://example.com/cover.jpg\n' + + ':isbn: 978-0-123456-78-9\n' + + ':source: https://github.com/alexandria/complex\n' + + ':auto-update: yes\n' + + '\n' + + 'This is the preamble content.\n' + + '\n' + + '== Section with Complex Metadata\n' + + ':author: Section Author\n' + + ':author: Section Co-Author\n' + + ':summary: This section has complex metadata\n' + + ':description: Alternative description for section\n' + + ':keywords: section, complex, metadata\n' + + ':tags: section, tags\n' + + ':type: chapter\n' + + ':image: https://example.com/section-image.jpg\n' + + '\n' + + 'This is the section content.', + sections: [ + { + metadata: [Object], + content: 'This is the section content.', + title: 'Section with Complex Metadata' + } + ] +} +Index event: { + documentTitle: 'Complex Metadata Document', + indexDTag: 'complex-metadata-document' +} +Creating section 0: { + title: 'Section with Complex Metadata', + dTag: 'complex-metadata-document-section-with-complex-metadata', + content: 'This is the section content.', + metadata: { + title: 'Section with Complex Metadata', + authors: [ 'Section Author', 'Section Co-Author' ], + summary: 'This section has complex metadata Alternative description for section', + type: 'chapter', + coverImage: 'https://example.com/section-image.jpg', + tags: [ 'section', 'tags', 'complex', 'metadata' ] + } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'type', 'complex' ], + [ 'title', 'Complex Metadata Document' ], + [ 'author', 'Jane Smith' ], + [ 'author', 'Override Author' ], + [ 'author', 'Third Author' ], + [ 'author', 'Section Author' ], + [ 'author', 'Section Co-Author' ], + [ 'version', '2.0' ], + [ 'published_on', '2024-03-01' ], + [ 'published_by', 'Alexandria Complex' ], + [ + 'summary', + 'This is a complex document with all metadata types Alternative description field' + ], + [ 'image', 'https://example.com/cover.jpg' ], + [ 'i', '978-0-123456-78-9' ], + [ 'source', 'https://github.com/alexandria/complex' ], + [ 'type', 'book' ], + [ 'auto-update', 'yes' ], + [ 't', 'additional' ], + [ 't', 'tags' ], + [ 't', 'here' ], + [ 't', 'complex' ], + [ 't', 'metadata' ], + [ 't', 'all-types' ], + [ 'd', 'complex-metadata-document' ], + [ 'title', 'Complex Metadata Document' ], + [ + 'a', + '30041:test-pubkey:complex-metadata-document-section-with-complex-metadata' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with only title and no sections +Parsed AsciiDoc: { + metadata: { + title: 'Document with No Sections', + version: 'Version', + summary: 'This document has no sections' + }, + content: '= Document with No Sections\n' + + ':summary: This document has no sections\n' + + '\n' + + 'This is just preamble content.', + sections: [] +} +Index event: { + documentTitle: 'Document with No Sections', + indexDTag: 'document-with-no-sections' +} +A tags: [] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with No Sections' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has no sections' ], + [ 'd', 'document-with-no-sections' ], + [ 'title', 'Document with No Sections' ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with special characters in title +Parsed AsciiDoc: { + metadata: { + title: 'Document with Special Characters: Test & More!', + version: 'Version', + summary: 'This document has special characters in the title' + }, + content: '= Document with Special Characters: Test & More!\n' + + ':summary: This document has special characters in the title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'Document with Special Characters: Test & More!', + indexDTag: 'document-with-special-characters-test-more' +} +Creating section 0: { + title: 'Section 1', + dTag: 'document-with-special-characters-test-more-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ 'title', 'Document with Special Characters: Test & More!' ], + [ 'version', 'Version' ], + [ 'summary', 'This document has special characters in the title' ], + [ 'd', 'document-with-special-characters-test-more' ], + [ 'title', 'Document with Special Characters: Test & More!' ], + [ + 'a', + '30041:test-pubkey:document-with-special-characters-test-more-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + +stdout | tests/unit/eventInput30040.test.ts > EventInput 30040 Publishing > Edge Cases > should handle document with very long title +Parsed AsciiDoc: { + metadata: { + title: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + version: 'Version', + summary: 'This document has a very long title' + }, + content: '= This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality\n' + + ':summary: This document has a very long title\n' + + '\n' + + '== Section 1\n' + + '\n' + + 'Content here.', + sections: [ + { + metadata: [Object], + content: 'Content here.', + title: 'Section 1' + } + ] +} +Index event: { + documentTitle: 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality', + indexDTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' +} +Creating section 0: { + title: 'Section 1', + dTag: 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1', + content: 'Content here.', + metadata: { title: 'Section 1' } +} +A tags: [ + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] +] +Final index event: { + kind: 30040, + content: '', + tags: [ + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ 'version', 'Version' ], + [ 'summary', 'This document has a very long title' ], + [ + 'd', + 'this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality' + ], + [ + 'title', + 'This is a very long document title that should be handled properly by the system and should not cause any issues with the d-tag generation or any other functionality' + ], + [ + 'a', + '30041:test-pubkey:this-is-a-very-long-document-title-that-should-be-handled-properly-by-the-system-and-should-not-cause-any-issues-with-the-d-tag-generation-or-any-other-functionality-section-1' + ] + ], + pubkey: 'test-pubkey', + created_at: 1234567890, + id: 'mock-event-id', + sig: 'mock-signature' +} +=== build30040EventSet completed === + + ✓ tests/unit/eventInput30040.test.ts (14 tests) 517ms + +⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > extractDocumentMetadata should extract document metadata correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:44:30 + 42| + 43| expect(metadata.title).toBe("Test Document with Metadata"); + 44| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 45| expect(metadata.version).toBe("1.0"); + 46| expect(metadata.publicationDate).toBe("2024-01-15"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯ + + FAIL tests/unit/metadataExtraction.test.ts > AsciiDoc Metadata Extraction > Smart metadata extraction > should handle document with full header correctly +AssertionError: expected [ 'John Doe', 'Jane Smith', …(1) ] to deeply equal [ 'John Doe', 'Jane Smith' ] + +- Expected ++ Received + + [ + "John Doe", + "Jane Smith", ++ "Section Author", + ] + + ❯ tests/unit/metadataExtraction.test.ts:318:32 + 316| // Should extract document-level metadata + 317| expect(metadata.title).toBe("Test Document"); + 318| expect(metadata.authors).toEqual(["John Doe", "Jane Smith"]); + | ^ + 319| expect(metadata.version).toBe("1.0"); + 320| expect(metadata.publishedBy).toBe("Alexandria Test"); + +⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯ + + + Test Files 1 failed | 1 passed (2) + Tests 2 failed | 28 passed (30) + Start at 13:21:50 + Duration 1.36s + + FAIL Tests failed. Watching for file changes... + press h to show help, press q to quit diff --git a/tests/unit/latexRendering.test.ts b/tests/unit/latexRendering.test.ts deleted file mode 100644 index eac80c5..0000000 --- a/tests/unit/latexRendering.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { parseAdvancedmarkup } from "../../src/lib/utils/markup/advancedMarkupParser"; -import { readFileSync } from "fs"; -import { join } from "path"; - -describe("LaTeX and AsciiMath Rendering in Inline Code Blocks", () => { - const jsonPath = join(__dirname, "../../test_data/LaTeXtestfile.json"); - const raw = readFileSync(jsonPath, "utf-8"); - // Extract the markdown content field from the JSON event - const content = JSON.parse(raw).content; - - it("renders LaTeX inline and display math correctly", async () => { - const html = await parseAdvancedmarkup(content); - // Test basic LaTeX examples from the test document - expect(html).toMatch(/\$\\sqrt\{x\}\$<\/span>/); - expect(html).toMatch(/
\$\$\\sqrt\{x\}\$\$<\/div>/); - expect(html).toMatch( - /\$\\mathbb\{N\} = \\{ a \\in \\mathbb\{Z\} : a > 0 \\}\$<\/span>/, - ); - expect(html).toMatch( - /
\$\$P \\left\( A=2 \\, \\middle\| \\, \\dfrac\{A\^2\}\{B\}>4 \\right\)\$\$<\/div>/, - ); - }); - - it("renders AsciiMath inline and display math correctly", async () => { - const html = await parseAdvancedmarkup(content); - // Test AsciiMath examples - expect(html).toMatch(/\$E=mc\^2\$<\/span>/); - expect(html).toMatch( - /
\$\$sum_\(k=1\)\^n k = 1\+2\+ cdots \+n=\(n\(n\+1\)\)\/2\$\$<\/div>/, - ); - expect(html).toMatch( - /
\$\$int_0\^1 x\^2 dx\$\$<\/div>/, - ); - }); - - it("renders LaTeX array and matrix environments as math", async () => { - const html = await parseAdvancedmarkup(content); - // Test array and matrix environments - expect(html).toMatch( - /
\$\$[\s\S]*\\begin\{array\}\{ccccc\}[\s\S]*\\end\{array\}[\s\S]*\$\$<\/div>/, - ); - expect(html).toMatch( - /
\$\$[\s\S]*\\begin\{bmatrix\}[\s\S]*\\end\{bmatrix\}[\s\S]*\$\$<\/div>/, - ); - }); - - it("handles unsupported LaTeX environments gracefully", async () => { - const html = await parseAdvancedmarkup(content); - // Should show a message and plaintext for tabular - expect(html).toMatch(/
/); - expect(html).toMatch( - /Unrendered, as it is LaTeX typesetting, not a formula:/, - ); - expect(html).toMatch(/\\\\begin\{tabular\}/); - }); - - it("renders mixed LaTeX and AsciiMath correctly", async () => { - const html = await parseAdvancedmarkup(content); - // Test mixed content - expect(html).toMatch( - /\$\\frac\{1\}\{2\}\$<\/span>/, - ); - expect(html).toMatch(/\$1\/2\$<\/span>/); - expect(html).toMatch( - /
\$\$\\sum_\{i=1\}\^n x_i\$\$<\/div>/, - ); - expect(html).toMatch( - /
\$\$sum_\(i=1\)\^n x_i\$\$<\/div>/, - ); - }); - - it("handles edge cases and regular code blocks", async () => { - const html = await parseAdvancedmarkup(content); - // Test regular code blocks (should remain as code, not math) - expect(html).toMatch(/]*>\$19\.99<\/code>/); - expect(html).toMatch(/]*>echo "Price: \$100"<\/code>/); - expect(html).toMatch( - /]*>const price = \\`\$\$\{amount\}\\`<\/code>/, - ); - expect(html).toMatch(/]*>color: \$primary-color<\/code>/); - }); -}); diff --git a/tests/unit/mathProcessing.test.ts b/tests/unit/mathProcessing.test.ts new file mode 100644 index 0000000..acf7378 --- /dev/null +++ b/tests/unit/mathProcessing.test.ts @@ -0,0 +1,186 @@ +import { describe, expect, it } from "vitest"; +import { parseAdvancedmarkup } from "../../src/lib/utils/markup/advancedMarkupParser.ts"; + +describe("Math Processing in Advanced Markup Parser", () => { + it("should process inline math inside code blocks", async () => { + const input = "Here is some inline math: `$x^2 + y^2 = z^2$` in a sentence."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain('\\(x^2 + y^2 = z^2\\)'); + expect(result).toContain("Here is some inline math:"); + expect(result).toContain("in a sentence."); + }); + + it("should process display math inside code blocks", async () => { + const input = "Here is a display equation:\n\n`$$\n\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}\n$$`\n\nThis is after the equation."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain('\\[\n\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}\n\\]'); + expect(result).toContain('

Here is a display equation:

'); + expect(result).toContain('

This is after the equation.

'); + }); + + it("should process both inline and display math in the same code block", async () => { + const input = "Mixed math: `$\\alpha$ and $$\\beta = \\frac{1}{2}$$` in one block."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain('\\(\\alpha\\)'); + expect(result).toContain('\\[\\beta = \\frac{1}{2}\\]'); + expect(result).toContain("Mixed math:"); + expect(result).toContain("in one block."); + }); + + it("should NOT process math outside of code blocks", async () => { + const input = "This math $x^2 + y^2 = z^2$ should not be processed."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain("$x^2 + y^2 = z^2$"); + expect(result).not.toContain(''); + expect(result).not.toContain(''); + }); + + it("should NOT process display math outside of code blocks", async () => { + const input = "This display math $$\n\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}\n$$ should not be processed."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain("$$\n\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}\n$$"); + expect(result).not.toContain(''); + expect(result).not.toContain(''); + }); + + it("should handle code blocks without math normally", async () => { + const input = "Here is some code: `console.log('hello world')` that should not be processed."; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain("`console.log('hello world')`"); + expect(result).not.toContain(''); + expect(result).not.toContain(''); + }); + + it("should handle complex math expressions with nested structures", async () => { + const input = "Complex math: `$$\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} \\cdot \\begin{pmatrix} x \\\\ y \\end{pmatrix} = \\begin{pmatrix} ax + by \\\\ cx + dy \\end{pmatrix}$$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain(''); + expect(result).toContain("\\begin{pmatrix}"); + expect(result).toContain("\\end{pmatrix}"); + expect(result).toContain("\\cdot"); + }); + + it("should handle inline math with special characters", async () => { + const input = "Special chars: `$\\alpha, \\beta, \\gamma, \\delta$` and `$\\sum_{i=1}^{n} x_i$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain('\\(\\alpha, \\beta, \\gamma, \\delta\\)'); + expect(result).toContain('\\(\\sum_{i=1}^{n} x_i\\)'); + }); + + it("should handle multiple math expressions in separate code blocks", async () => { + const input = "First: `$E = mc^2$` and second: `$$F = G\\frac{m_1 m_2}{r^2}$$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain('\\(E = mc^2\\)'); + expect(result).toContain('\\[F = G\\frac{m_1 m_2}{r^2}\\]'); + }); + + it("should handle math expressions with line breaks in display mode", async () => { + const input = "Multi-line: `$$\n\\begin{align}\nx &= a + b \\\\\ny &= c + d\n\\end{align}\n$$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain(''); + expect(result).toContain("\\begin{align}"); + expect(result).toContain("\\end{align}"); + expect(result).toContain("x &= a + b"); + expect(result).toContain("y &= c + d"); + }); + + it("should handle edge case with empty math expressions", async () => { + const input = "Empty math: `$$` and `$`"; + const result = await parseAdvancedmarkup(input); + + // Should not crash and should preserve the original content + expect(result).toContain("`$$`"); + expect(result).toContain("`$`"); + }); + + it("should handle mixed content with regular text, code, and math", async () => { + const input = `This is a paragraph with regular text. + +Here is some code: \`console.log('hello')\` + +And here is math: \`$\\pi \\approx 3.14159$\` + +And display math: \`$$\n\\int_0^1 x^2 dx = \\frac{1}{3}\n$$\` + +And more regular text.`; + + const result = await parseAdvancedmarkup(input); + + // Should preserve regular text + expect(result).toContain("This is a paragraph with regular text."); + expect(result).toContain("And more regular text."); + + // Should preserve regular code blocks + expect(result).toContain("`console.log('hello')`"); + + // Should process math + expect(result).toContain('\\(\\pi \\approx 3.14159\\)'); + expect(result).toContain(''); + expect(result).toContain("\\int_0^1 x^2 dx = \\frac{1}{3}"); + }); + + it("should handle math expressions with dollar signs in the content", async () => { + const input = "Price math: `$\\text{Price} = \\$19.99$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain(''); + expect(result).toContain("\\text{Price} = \\$19.99"); + }); + + it("should handle display math with dollar signs in the content", async () => { + const input = "Price display: `$$\n\\text{Total} = \\$19.99 + \\$5.99 = \\$25.98\n$$`"; + const result = await parseAdvancedmarkup(input); + + expect(result).toContain(''); + expect(result).toContain("\\text{Total} = \\$19.99 + \\$5.99 = \\$25.98"); + }); + + it("should handle JSON content with escaped backslashes", async () => { + // Simulate content from JSON where backslashes are escaped + const jsonContent = "Math from JSON: `$\\\\alpha + \\\\beta = \\\\gamma$`"; + const result = await parseAdvancedmarkup(jsonContent); + + expect(result).toContain(''); + expect(result).toContain("\\\\alpha + \\\\beta = \\\\gamma"); + }); + + it("should handle JSON content with escaped display math", async () => { + // Simulate content from JSON where backslashes are escaped + const jsonContent = "Display math from JSON: `$$\\\\int_0^1 x^2 dx = \\\\frac{1}{3}$$`"; + const result = await parseAdvancedmarkup(jsonContent); + + expect(result).toContain(''); + expect(result).toContain("\\\\int_0^1 x^2 dx = \\\\frac{1}{3}"); + }); + + it("should handle JSON content with escaped dollar signs", async () => { + // Simulate content from JSON where dollar signs are escaped + const jsonContent = "Price math from JSON: `$\\\\text{Price} = \\\\\\$19.99$`"; + const result = await parseAdvancedmarkup(jsonContent); + + expect(result).toContain(''); + expect(result).toContain("\\\\text{Price} = \\\\\\$19.99"); + }); + + it("should handle complex JSON content with multiple escaped characters", async () => { + // Simulate complex content from JSON + const jsonContent = "Complex JSON math: `$$\\\\begin{pmatrix} a & b \\\\\\\\ c & d \\\\end{pmatrix} \\\\cdot \\\\begin{pmatrix} x \\\\\\\\ y \\\\end{pmatrix}$$`"; + const result = await parseAdvancedmarkup(jsonContent); + + expect(result).toContain(''); + expect(result).toContain("\\\\begin{pmatrix}"); + expect(result).toContain("\\\\end{pmatrix}"); + expect(result).toContain("\\\\cdot"); + expect(result).toContain("\\\\\\\\"); + }); +}); diff --git a/tests/unit/tagExpansion.test.ts b/tests/unit/tagExpansion.test.ts index cc55fb9..e47f74b 100644 --- a/tests/unit/tagExpansion.test.ts +++ b/tests/unit/tagExpansion.test.ts @@ -74,11 +74,14 @@ vi.mock("../../src/lib/utils/profileCache", () => ({ batchFetchProfiles: vi.fn( async ( pubkeys: string[], - onProgress: (fetched: number, total: number) => void, + ndk: any, + onProgress?: (fetched: number, total: number) => void, ) => { // Simulate progress updates - onProgress(0, pubkeys.length); - onProgress(pubkeys.length, pubkeys.length); + if (onProgress) { + onProgress(0, pubkeys.length); + onProgress(pubkeys.length, pubkeys.length); + } return []; }, ),