/** * Content Extractor Tests (v7.9.0) * * Tests for content type detection and text extraction from * structured formats (rich-text JSON, HTML, Markdown). */ import { describe, it, expect } from 'vitest' import { detectContentType, extractForHighlighting } from '../../../src/utils/contentExtractor' describe('Content Extractor (v7.9.0)', () => { describe('detectContentType()', () => { it('should detect plaintext', () => { expect(detectContentType('Hello world')).toBe('plaintext') expect(detectContentType('Just some regular text.')).toBe('plaintext') }) it('should detect rich-text JSON (object)', () => { const tiptap = JSON.stringify({ type: 'doc', content: [] }) expect(detectContentType(tiptap)).toBe('richtext-json') }) it('should detect rich-text JSON (array)', () => { const slate = JSON.stringify([{ type: 'paragraph', children: [{ text: 'hi' }] }]) expect(detectContentType(slate)).toBe('richtext-json') }) it('should detect HTML', () => { expect(detectContentType('

Title

')).toBe('html') expect(detectContentType('
content
')).toBe('html') expect(detectContentType('')).toBe('html') }) it('should detect Markdown', () => { expect(detectContentType('# Heading\n\nSome text')).toBe('markdown') expect(detectContentType('```\ncode\n```')).toBe('markdown') }) it('should handle invalid JSON as plaintext', () => { expect(detectContentType('{ broken json')).toBe('plaintext') expect(detectContentType('[not valid')).toBe('plaintext') }) it('should handle whitespace-prefixed JSON', () => { const json = ' { "type": "doc" }' expect(detectContentType(json)).toBe('richtext-json') }) }) describe('extractForHighlighting() — TipTap/ProseMirror', () => { it('should extract text nodes with content categories', () => { const doc = JSON.stringify({ type: 'doc', content: [ { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'My Heading' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Regular paragraph text' }] }, { type: 'codeBlock', content: [{ type: 'text', text: 'const x = 1' }] } ] }) const segments = extractForHighlighting(doc) expect(segments.length).toBe(3) expect(segments[0].text).toBe('My Heading') expect(segments[0].contentCategory).toBe('title') expect(segments[1].text).toBe('Regular paragraph text') expect(segments[1].contentCategory).toBe('content') expect(segments[2].text).toBe('const x = 1') expect(segments[2].contentCategory).toBe('code') }) it('should handle nested content with marks', () => { const doc = JSON.stringify({ type: 'doc', content: [ { type: 'paragraph', content: [ { type: 'text', text: 'Hello ' }, { type: 'text', text: 'bold world', marks: [{ type: 'bold' }] } ] } ] }) const segments = extractForHighlighting(doc) expect(segments.length).toBe(2) expect(segments[0].text).toBe('Hello ') expect(segments[1].text).toBe('bold world') }) }) describe('extractForHighlighting() — Slate.js', () => { it('should extract text from Slate children nodes', () => { const doc = JSON.stringify([ { type: 'heading', children: [{ text: 'Slate Heading' }] }, { type: 'paragraph', children: [ { text: 'First part ' }, { text: 'second part', bold: true } ] } ]) const segments = extractForHighlighting(doc) expect(segments.length).toBe(3) expect(segments[0].text).toBe('Slate Heading') expect(segments[0].contentCategory).toBe('title') expect(segments[1].text).toBe('First part ') expect(segments[1].contentCategory).toBe('content') }) }) describe('extractForHighlighting() — Lexical', () => { it('should extract text from Lexical root structure', () => { const doc = JSON.stringify({ root: { children: [ { type: 'heading', children: [{ type: 'text', text: 'Lexical Heading' }] }, { type: 'paragraph', children: [{ type: 'text', text: 'Lexical body text' }] } ] } }) const segments = extractForHighlighting(doc) expect(segments.length).toBe(2) expect(segments[0].text).toBe('Lexical Heading') expect(segments[0].contentCategory).toBe('title') expect(segments[1].text).toBe('Lexical body text') expect(segments[1].contentCategory).toBe('content') }) }) describe('extractForHighlighting() — Draft.js', () => { it('should extract text from Draft.js blocks', () => { const doc = JSON.stringify({ blocks: [ { type: 'header-one', text: 'Draft Heading' }, { type: 'unstyled', text: 'Draft body text' } ] }) const segments = extractForHighlighting(doc) expect(segments.length).toBeGreaterThanOrEqual(2) // Draft.js blocks have text property directly with type const headingSegment = segments.find(s => s.text === 'Draft Heading') const bodySegment = segments.find(s => s.text === 'Draft body text') expect(headingSegment).toBeDefined() expect(bodySegment).toBeDefined() }) }) describe('extractForHighlighting() — Quill Delta', () => { it('should extract text from Quill Delta ops', () => { const doc = JSON.stringify({ ops: [ { insert: 'Hello World\n' }, { insert: 'Second line\n' } ] }) const segments = extractForHighlighting(doc) expect(segments.length).toBe(2) expect(segments[0].text).toContain('Hello World') expect(segments[1].text).toContain('Second line') }) }) describe('extractForHighlighting() — Generic JSON fallback', () => { it('should extract all string values from unrecognized JSON', () => { const generic = JSON.stringify({ name: 'David', description: 'A brave warrior', stats: { strength: 10 } }) const segments = extractForHighlighting(generic) expect(segments.length).toBeGreaterThan(0) const allText = segments.map(s => s.text).join(' ') expect(allText).toContain('David') expect(allText).toContain('brave warrior') }) it('should return empty for JSON with no text', () => { const noText = JSON.stringify({ count: 42, active: true }) const segments = extractForHighlighting(noText) // No string values to extract expect(segments).toBeDefined() }) }) describe('extractForHighlighting() — HTML', () => { it('should extract headings, paragraphs, and code', () => { const html = '

Title

Body text here.

let x = 1
' const segments = extractForHighlighting(html) expect(segments.length).toBeGreaterThanOrEqual(3) const headingSegment = segments.find(s => s.text === 'Title') expect(headingSegment).toBeDefined() expect(headingSegment?.contentCategory).toBe('title') const bodySegment = segments.find(s => s.text === 'Body text here.') expect(bodySegment).toBeDefined() expect(bodySegment?.contentCategory).toBe('content') const codeSegment = segments.find(s => s.text === 'let x = 1') expect(codeSegment).toBeDefined() expect(codeSegment?.contentCategory).toBe('code') }) it('should skip script and style content', () => { const html = '

Visible

Also visible

' const segments = extractForHighlighting(html) const allText = segments.map(s => s.text).join(' ') expect(allText).toContain('Visible') expect(allText).toContain('Also visible') expect(allText).not.toContain('alert') expect(allText).not.toContain('.foo') }) it('should decode common HTML entities', () => { const html = '

Tom & Jerry <3

' const segments = extractForHighlighting(html) expect(segments.length).toBeGreaterThan(0) expect(segments[0].text).toContain('Tom & Jerry <3') }) it('should handle nested heading tags', () => { const html = '

Nested Heading

' const segments = extractForHighlighting(html) const heading = segments.find(s => s.text === 'Nested Heading') expect(heading).toBeDefined() expect(heading?.contentCategory).toBe('title') }) }) describe('extractForHighlighting() — Markdown', () => { it('should extract headings', () => { const md = '# Main Title\n\nSome body text\n\n## Subtitle' const segments = extractForHighlighting(md) const heading1 = segments.find(s => s.text === 'Main Title') const heading2 = segments.find(s => s.text === 'Subtitle') const body = segments.find(s => s.text === 'Some body text') expect(heading1).toBeDefined() expect(heading1?.contentCategory).toBe('title') expect(heading2).toBeDefined() expect(heading2?.contentCategory).toBe('title') expect(body).toBeDefined() expect(body?.contentCategory).toBe('content') }) it('should extract fenced code blocks', () => { const md = '# Title\n\n```typescript\nconst x = 1\nconst y = 2\n```\n\nMore text' const segments = extractForHighlighting(md) const code = segments.find(s => s.contentCategory === 'code') expect(code).toBeDefined() expect(code?.text).toContain('const x = 1') }) it('should extract indented code blocks', () => { // Indented code alone doesn't trigger markdown detection, so pass explicit type const md = 'Normal text\n\n code line 1\n code line 2\n\nMore text' const segments = extractForHighlighting(md, 'markdown') const code = segments.find(s => s.contentCategory === 'code') expect(code).toBeDefined() expect(code?.text).toContain('code line 1') }) it('should split inline code spans into separate segments', () => { const md = '# Title\n\nUse the `authenticate()` function to verify users' const segments = extractForHighlighting(md) // Should have: title segment, content "Use the", code "authenticate()", content "function to verify users" const codeSegment = segments.find(s => s.text === 'authenticate()' && s.contentCategory === 'code') expect(codeSegment).toBeDefined() const beforeCode = segments.find(s => s.text === 'Use the' && s.contentCategory === 'content') expect(beforeCode).toBeDefined() const afterCode = segments.find(s => s.text === 'function to verify users' && s.contentCategory === 'content') expect(afterCode).toBeDefined() }) it('should handle multiple inline code spans in a single line', () => { const md = '# API\n\nCall `foo()` then `bar()` for results' const segments = extractForHighlighting(md, 'markdown') const fooSegment = segments.find(s => s.text === 'foo()' && s.contentCategory === 'code') const barSegment = segments.find(s => s.text === 'bar()' && s.contentCategory === 'code') expect(fooSegment).toBeDefined() expect(barSegment).toBeDefined() }) it('should not split backticks inside fenced code blocks', () => { const md = '# Title\n\n```\nconst x = `template`\n```' const segments = extractForHighlighting(md) const code = segments.find(s => s.contentCategory === 'code') expect(code).toBeDefined() // The fenced block content should be intact, not split by backticks expect(code?.text).toContain('const x = `template`') }) }) describe('extractForHighlighting() — Plaintext', () => { it('should return single content segment for plain text', () => { const text = 'Just some plain text content' const segments = extractForHighlighting(text) expect(segments.length).toBe(1) expect(segments[0].text).toBe(text) expect(segments[0].contentCategory).toBe('content') }) it('should respect contentType override', () => { // Even if content looks like HTML, plaintext hint should skip detection const html = '

Title

' const segments = extractForHighlighting(html, 'plaintext') expect(segments.length).toBe(1) expect(segments[0].text).toBe(html) // Raw HTML, not extracted expect(segments[0].contentCategory).toBe('content') }) }) })