import { describe, expect, test } from 'bun:test'; import { MarkdownConverter } from '../lib/markdown/converter.js'; import { HtmlConverter } from '../lib/markdown/html-converter.js'; import { parseMarkdown } from '../lib/markdown/frontmatter.js'; import { buildPageLookupMapFromCache } from '../lib/markdown/link-converter.js'; import type { PageStateCache, FullPageInfo } from '../lib/page-state.js'; /** * Helper to create PageStateCache from a simple record */ function createPageStateCache( pages: Record, ): PageStateCache { const pagesMap = new Map(); const pathToPageId = new Map(); for (const [pageId, info] of Object.entries(pages)) { pagesMap.set(pageId, { pageId: info.pageId, localPath: info.localPath, title: info.title, version: 1, }); pathToPageId.set(info.localPath, pageId); } return { pages: pagesMap, pathToPageId }; } describe('Link Conversion Integration Tests', () => { // TODO: This test requires enhancements to turndown parsing of ac:link elements // The link conversion logic is implemented correctly, but turndown needs custom // handling for CDATA content in ac:plain-text-link-body elements test.skip('full pull-modify-push cycle converts links correctly', () => { // Setup: Create mock page state with multiple pages // Per ADR-0024: Use PageStateCache built from frontmatter const pageState = createPageStateCache({ 'page-1': { pageId: 'page-1', localPath: 'home.md', title: 'Home', }, 'page-2': { pageId: 'page-2', localPath: 'architecture/overview.md', title: 'Architecture Overview', }, 'page-3': { pageId: 'page-3', localPath: 'architecture/database.md', title: 'Database Design', }, }); const pageLookupMap = buildPageLookupMapFromCache(pageState); // Step 1: PULL - Convert Confluence HTML with page links to markdown const confluenceHtml = `

Welcome to our documentation!

Check out the for design details.

Also see .

`; const markdownConverter = new MarkdownConverter(); const { markdown, warnings: pullWarnings } = markdownConverter.convertPage( { id: 'page-1', status: 'current', title: 'Home', spaceId: 'space-123', authorId: 'user-1', body: { storage: { value: confluenceHtml, representation: 'storage', }, }, version: { number: 1, createdAt: '2024-01-01T00:00:00Z', }, }, [], undefined, '', undefined, undefined, 'home.md', pageLookupMap, ); // Verify: Links were converted to relative paths during pull expect(markdown).toContain('[Architecture](./architecture/overview.md)'); expect(markdown).toContain('[Database Schema](./architecture/database.md)'); expect(markdown).not.toContain('ri:page'); expect(pullWarnings.length).toBe(0); // Step 2: Simulate user modifying the markdown locally // Extract just the content (without frontmatter) for the push const { content: markdownContent } = parseMarkdown(markdown); // Step 3: PUSH - Convert markdown with relative paths back to Confluence HTML const htmlConverter = new HtmlConverter(); const { html, warnings: pushWarnings } = htmlConverter.convert( markdownContent, '/test/space', 'home.md', 'TEST', pageLookupMap, ); // Verify: Links were converted back to Confluence format during push expect(html).toContain(''); expect(html).toContain('ri:page ri:content-title="Architecture Overview"'); expect(html).toContain('ri:space-key="TEST"'); expect(html).toContain('ri:page ri:content-title="Database Design"'); expect(html).toContain(''); expect(html).toContain(''); expect(pushWarnings.length).toBe(0); // Verify: Round-trip conversion preserves link semantics // (The exact formatting might differ slightly, but the meaning should be the same) expect(html).not.toContain('./architecture/overview.md'); expect(html).not.toContain('./architecture/database.md'); }); // TODO: Same issue as above - requires turndown enhancement test.skip('handles broken links gracefully during pull and push', () => { // Per ADR-0024: Use PageStateCache built from frontmatter const pageState = createPageStateCache({ 'page-1': { pageId: 'page-1', localPath: 'home.md', title: 'Home', }, }); const pageLookupMap = buildPageLookupMapFromCache(pageState); // PULL: Link to non-existent page in Confluence const confluenceHtml = `

See .

`; const markdownConverter = new MarkdownConverter(); const { markdown, warnings: pullWarnings } = markdownConverter.convertPage( { id: 'page-1', status: 'current', title: 'Home', spaceId: 'space-123', authorId: 'user-1', body: { storage: { value: confluenceHtml, representation: 'storage', }, }, version: { number: 1, createdAt: '2024-01-01T00:00:00Z', }, }, [], undefined, '', undefined, undefined, 'home.md', pageLookupMap, ); // Should preserve original content and warn expect(pullWarnings.length).toBeGreaterThan(0); expect(pullWarnings[0]).toContain('could not be resolved'); expect(markdown).not.toContain('./non-existent-page.md'); // PUSH: Link to non-existent local file const localMarkdown = 'See [this page](./non-existent.md).'; const htmlConverter = new HtmlConverter(); const { html, warnings: pushWarnings } = htmlConverter.convert( localMarkdown, '/test/space', 'home.md', 'TEST', pageLookupMap, ); // Should fall back to HTML link and warn expect(pushWarnings.length).toBeGreaterThan(0); expect(pushWarnings[0]).toContain('could not be resolved'); expect(html).toContain(''); expect(html).not.toContain('ri:page'); }); });