pyWebLayout/tests/abstract/test_abstract_document.py

697 lines
24 KiB
Python

"""
Unit tests for abstract document elements.
Tests the Document, Chapter, Book, and MetadataType classes that handle
document structure and metadata management.
"""
import unittest
from pyWebLayout.abstract.document import Document, Chapter, Book, MetadataType
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, BlockType
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style import Font, FontWeight, FontStyle, TextDecoration
class TestMetadataType(unittest.TestCase):
"""Test cases for MetadataType enum."""
def test_metadata_types(self):
"""Test that all expected metadata types exist."""
expected_types = [
'TITLE', 'AUTHOR', 'DESCRIPTION', 'KEYWORDS', 'LANGUAGE',
'PUBLICATION_DATE', 'MODIFIED_DATE', 'PUBLISHER', 'IDENTIFIER',
'COVER_IMAGE', 'CUSTOM'
]
for type_name in expected_types:
self.assertTrue(hasattr(MetadataType, type_name))
# Test custom type has expected value
self.assertEqual(MetadataType.CUSTOM.value, 100)
class TestDocument(unittest.TestCase):
"""Test cases for Document class."""
def setUp(self):
"""Set up test fixtures."""
self.doc = Document("Test Document", "en-US")
self.font = Font()
def test_document_creation(self):
"""Test document creation with basic parameters."""
self.assertEqual(self.doc.get_title(), "Test Document")
self.assertEqual(self.doc.get_metadata(MetadataType.LANGUAGE), "en-US")
self.assertEqual(len(self.doc.blocks), 0)
def test_document_creation_minimal(self):
"""Test document creation with minimal parameters."""
doc = Document()
self.assertIsNone(doc.get_title())
self.assertEqual(doc.get_metadata(MetadataType.LANGUAGE), "en-US")
def test_metadata_management(self):
"""Test setting and getting metadata."""
# Set various metadata types
self.doc.set_metadata(MetadataType.AUTHOR, "John Doe")
self.doc.set_metadata(MetadataType.DESCRIPTION, "A test document")
self.doc.set_metadata(MetadataType.KEYWORDS, ["test", "document"])
# Test retrieval
self.assertEqual(self.doc.get_metadata(MetadataType.AUTHOR), "John Doe")
self.assertEqual(
self.doc.get_metadata(
MetadataType.DESCRIPTION),
"A test document")
self.assertEqual(
self.doc.get_metadata(
MetadataType.KEYWORDS), [
"test", "document"])
# Test non-existent metadata
self.assertIsNone(self.doc.get_metadata(MetadataType.PUBLISHER))
def test_title_convenience_methods(self):
"""Test title getter and setter convenience methods."""
# Test setting title
self.doc.set_title("New Title")
self.assertEqual(self.doc.get_title(), "New Title")
# Test that it's also in metadata
self.assertEqual(self.doc.get_metadata(MetadataType.TITLE), "New Title")
def test_block_management(self):
"""Test adding and managing blocks."""
# Create some blocks
para1 = Paragraph()
para2 = Paragraph()
heading = Heading(HeadingLevel.H1)
# Add blocks
self.doc.add_block(para1)
self.doc.add_block(heading)
self.doc.add_block(para2)
# Test blocks list
self.assertEqual(len(self.doc.blocks), 3)
self.assertEqual(self.doc.blocks[0], para1)
self.assertEqual(self.doc.blocks[1], heading)
self.assertEqual(self.doc.blocks[2], para2)
def test_anchor_management(self):
"""Test named anchor functionality."""
heading = Heading(HeadingLevel.H1)
para = Paragraph()
# Add anchors
self.doc.add_anchor("intro", heading)
self.doc.add_anchor("content", para)
# Test retrieval
self.assertEqual(self.doc.get_anchor("intro"), heading)
self.assertEqual(self.doc.get_anchor("content"), para)
self.assertIsNone(self.doc.get_anchor("nonexistent"))
def test_resource_management(self):
"""Test document resource management."""
# Add various resources
self.doc.add_resource("image1", {"type": "image", "path": "test.jpg"})
self.doc.add_resource("style1", {"type": "css", "content": "body {}"})
# Test retrieval
image = self.doc.get_resource("image1")
self.assertEqual(image["type"], "image")
self.assertEqual(image["path"], "test.jpg")
style = self.doc.get_resource("style1")
self.assertEqual(style["type"], "css")
# Test non-existent resource
self.assertIsNone(self.doc.get_resource("nonexistent"))
def test_stylesheet_management(self):
"""Test stylesheet addition."""
# Add stylesheets
css1 = {"href": "style.css", "type": "text/css"}
css2 = {"href": "theme.css", "type": "text/css"}
self.doc.add_stylesheet(css1)
self.doc.add_stylesheet(css2)
# Test that stylesheets are stored
self.assertEqual(len(self.doc._stylesheets), 2)
self.assertEqual(self.doc._stylesheets[0], css1)
self.assertEqual(self.doc._stylesheets[1], css2)
def test_script_management(self):
"""Test script addition."""
# Add scripts
script1 = "console.log('Hello');"
script2 = "document.ready(function(){});"
self.doc.add_script(script1)
self.doc.add_script(script2)
# Test that scripts are stored
self.assertEqual(len(self.doc._scripts), 2)
self.assertEqual(self.doc._scripts[0], script1)
self.assertEqual(self.doc._scripts[1], script2)
def test_find_blocks_by_type(self):
"""Test finding blocks by type."""
# Create blocks of different types
para1 = Paragraph()
para2 = Paragraph()
heading1 = Heading(HeadingLevel.H1)
heading2 = Heading(HeadingLevel.H2)
# Add blocks to document
self.doc.add_block(para1)
self.doc.add_block(heading1)
self.doc.add_block(para2)
self.doc.add_block(heading2)
# Test finding paragraphs
paragraphs = self.doc.find_blocks_by_type(BlockType.PARAGRAPH)
self.assertEqual(len(paragraphs), 2)
self.assertIn(para1, paragraphs)
self.assertIn(para2, paragraphs)
# Test finding headings
headings = self.doc.find_blocks_by_type(BlockType.HEADING)
self.assertEqual(len(headings), 2)
self.assertIn(heading1, headings)
self.assertIn(heading2, headings)
def test_find_headings(self):
"""Test finding heading blocks specifically."""
# Create mixed blocks
para = Paragraph()
h1 = Heading(HeadingLevel.H1)
h2 = Heading(HeadingLevel.H2)
# Add words to headings for title extraction
word1 = Word("Chapter", self.font)
word2 = Word("One", self.font)
h1.add_word(word1)
h1.add_word(word2)
word3 = Word("Section", self.font)
h2.add_word(word3)
self.doc.add_block(para)
self.doc.add_block(h1)
self.doc.add_block(h2)
# Test finding headings
headings = self.doc.find_headings()
self.assertEqual(len(headings), 2)
self.assertIn(h1, headings)
self.assertIn(h2, headings)
self.assertNotIn(para, headings)
def test_generate_table_of_contents(self):
"""Test table of contents generation."""
# Create headings with content
h1 = Heading(HeadingLevel.H1)
h2 = Heading(HeadingLevel.H2)
h3 = Heading(HeadingLevel.H3)
# Add words to headings
h1.add_word(Word("Introduction", self.font))
h2.add_word(Word("Getting", self.font))
h2.add_word(Word("Started", self.font))
h3.add_word(Word("Installation", self.font))
self.doc.add_block(h1)
self.doc.add_block(h2)
self.doc.add_block(h3)
# Generate TOC
toc = self.doc.generate_table_of_contents()
# Test TOC structure
self.assertEqual(len(toc), 3)
# Test first entry
level, title, block = toc[0]
self.assertEqual(level, 1) # H1
self.assertEqual(title, "Introduction")
self.assertEqual(block, h1)
# Test second entry
level, title, block = toc[1]
self.assertEqual(level, 2) # H2
self.assertEqual(title, "Getting Started")
self.assertEqual(block, h2)
# Test third entry
level, title, block = toc[2]
self.assertEqual(level, 3) # H3
self.assertEqual(title, "Installation")
self.assertEqual(block, h3)
class TestChapter(unittest.TestCase):
"""Test cases for Chapter class."""
def setUp(self):
"""Set up test fixtures."""
self.chapter = Chapter("Test Chapter", 1)
def test_chapter_creation(self):
"""Test chapter creation."""
self.assertEqual(self.chapter.title, "Test Chapter")
self.assertEqual(self.chapter.level, 1)
self.assertEqual(len(self.chapter.blocks), 0)
def test_chapter_creation_minimal(self):
"""Test chapter creation with minimal parameters."""
chapter = Chapter()
self.assertIsNone(chapter.title)
self.assertEqual(chapter.level, 1)
def test_title_property(self):
"""Test title property getter and setter."""
# Test setter
self.chapter.title = "New Chapter Title"
self.assertEqual(self.chapter.title, "New Chapter Title")
# Test setting to None
self.chapter.title = None
self.assertIsNone(self.chapter.title)
def test_level_property(self):
"""Test level property."""
self.assertEqual(self.chapter.level, 1)
# Level should be read-only (no setter test)
# This is by design based on the class definition
def test_block_management(self):
"""Test adding blocks to chapter."""
para1 = Paragraph()
para2 = Paragraph()
heading = Heading(HeadingLevel.H2)
# Add blocks
self.chapter.add_block(para1)
self.chapter.add_block(heading)
self.chapter.add_block(para2)
# Test blocks list
self.assertEqual(len(self.chapter.blocks), 3)
self.assertEqual(self.chapter.blocks[0], para1)
self.assertEqual(self.chapter.blocks[1], heading)
self.assertEqual(self.chapter.blocks[2], para2)
def test_metadata_management(self):
"""Test chapter metadata."""
# Set metadata
self.chapter.set_metadata("author", "Jane Doe")
self.chapter.set_metadata("word_count", 1500)
self.chapter.set_metadata("tags", ["intro", "basics"])
# Test retrieval
self.assertEqual(self.chapter.get_metadata("author"), "Jane Doe")
self.assertEqual(self.chapter.get_metadata("word_count"), 1500)
self.assertEqual(self.chapter.get_metadata("tags"), ["intro", "basics"])
# Test non-existent metadata
self.assertIsNone(self.chapter.get_metadata("nonexistent"))
class TestBook(unittest.TestCase):
"""Test cases for Book class."""
def setUp(self):
"""Set up test fixtures."""
self.book = Book("Test Book", "Author Name", "en-US")
def test_book_creation(self):
"""Test book creation with all parameters."""
self.assertEqual(self.book.get_title(), "Test Book")
self.assertEqual(self.book.get_author(), "Author Name")
self.assertEqual(self.book.get_metadata(MetadataType.LANGUAGE), "en-US")
self.assertEqual(len(self.book.chapters), 0)
def test_book_creation_minimal(self):
"""Test book creation with minimal parameters."""
book = Book()
self.assertIsNone(book.get_title())
self.assertIsNone(book.get_author())
self.assertEqual(book.get_metadata(MetadataType.LANGUAGE), "en-US")
def test_book_creation_partial(self):
"""Test book creation with partial parameters."""
book = Book(title="Just Title")
self.assertEqual(book.get_title(), "Just Title")
self.assertIsNone(book.get_author())
def test_author_convenience_methods(self):
"""Test author getter and setter convenience methods."""
# Test setting author
self.book.set_author("New Author")
self.assertEqual(self.book.get_author(), "New Author")
# Test that it's also in metadata
self.assertEqual(self.book.get_metadata(MetadataType.AUTHOR), "New Author")
def test_chapter_management(self):
"""Test adding and managing chapters."""
# Create chapters
ch1 = Chapter("Introduction", 1)
ch2 = Chapter("Getting Started", 1)
ch3 = Chapter("Advanced Topics", 1)
# Add chapters
self.book.add_chapter(ch1)
self.book.add_chapter(ch2)
self.book.add_chapter(ch3)
# Test chapters list
self.assertEqual(len(self.book.chapters), 3)
self.assertEqual(self.book.chapters[0], ch1)
self.assertEqual(self.book.chapters[1], ch2)
self.assertEqual(self.book.chapters[2], ch3)
def test_create_chapter(self):
"""Test creating chapters through the book."""
# Create chapter with title and level
ch1 = self.book.create_chapter("Chapter 1", 1)
self.assertEqual(ch1.title, "Chapter 1")
self.assertEqual(ch1.level, 1)
self.assertEqual(len(self.book.chapters), 1)
self.assertEqual(self.book.chapters[0], ch1)
# Create chapter with minimal parameters
ch2 = self.book.create_chapter()
self.assertIsNone(ch2.title)
self.assertEqual(ch2.level, 1)
self.assertEqual(len(self.book.chapters), 2)
def test_generate_book_toc(self):
"""Test table of contents generation for book."""
# Create chapters with different levels
ch1 = Chapter("Introduction", 1)
ch2 = Chapter("Getting Started", 1)
ch3 = Chapter("Basic Concepts", 2)
ch4 = Chapter("Advanced Topics", 1)
ch5 = Chapter("Best Practices", 2)
# Add chapters to book
self.book.add_chapter(ch1)
self.book.add_chapter(ch2)
self.book.add_chapter(ch3)
self.book.add_chapter(ch4)
self.book.add_chapter(ch5)
# Generate TOC
toc = self.book.generate_table_of_contents()
# Test TOC structure
self.assertEqual(len(toc), 5)
# Test entries
expected = [
(1, "Introduction", ch1),
(1, "Getting Started", ch2),
(2, "Basic Concepts", ch3),
(1, "Advanced Topics", ch4),
(2, "Best Practices", ch5)
]
for i, (exp_level, exp_title, exp_chapter) in enumerate(expected):
level, title, chapter = toc[i]
self.assertEqual(level, exp_level)
self.assertEqual(title, exp_title)
self.assertEqual(chapter, exp_chapter)
def test_generate_book_toc_with_untitled_chapters(self):
"""Test TOC generation with chapters that have no title."""
# Create chapters, some without titles
ch1 = Chapter("Introduction", 1)
ch2 = Chapter(None, 1) # No title
ch3 = Chapter("Conclusion", 1)
self.book.add_chapter(ch1)
self.book.add_chapter(ch2)
self.book.add_chapter(ch3)
# Generate TOC
toc = self.book.generate_table_of_contents()
# Should only include chapters with titles
self.assertEqual(len(toc), 2)
level, title, chapter = toc[0]
self.assertEqual(title, "Introduction")
self.assertEqual(chapter, ch1)
level, title, chapter = toc[1]
self.assertEqual(title, "Conclusion")
self.assertEqual(chapter, ch3)
def test_book_inherits_document_features(self):
"""Test that Book inherits all Document functionality."""
# Test that book can use all document methods
# Add blocks directly to book
para = Paragraph()
self.book.add_block(para)
self.assertEqual(len(self.book.blocks), 1)
# Test metadata
self.book.set_metadata(MetadataType.PUBLISHER, "Test Publisher")
self.assertEqual(
self.book.get_metadata(
MetadataType.PUBLISHER),
"Test Publisher")
# Test anchors
heading = Heading(HeadingLevel.H1)
self.book.add_anchor("preface", heading)
self.assertEqual(self.book.get_anchor("preface"), heading)
class TestDocumentFontRegistry(unittest.TestCase):
"""Test cases for Document font registry functionality."""
def setUp(self):
"""Set up test fixtures."""
self.doc = Document("Test Document", "en-US")
def test_get_or_create_font_creates_new_font(self):
"""Test that get_or_create_font creates a new font when none exists."""
font = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
self.assertEqual(font.font_size, 14)
self.assertEqual(font.colour, (255, 0, 0))
self.assertEqual(font.weight, FontWeight.BOLD)
# Check that font is stored in registry
self.assertEqual(len(self.doc._fonts), 1)
def test_get_or_create_font_reuses_existing_font(self):
"""Test that get_or_create_font reuses existing fonts."""
# Create first font
font1 = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create second font with same properties
font2 = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Should return the same font object
self.assertIs(font1, font2)
# Should only have one font in registry
self.assertEqual(len(self.doc._fonts), 1)
def test_get_or_create_font_creates_different_fonts(self):
"""Test that different font properties create different fonts."""
# Create first font
font1 = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create font with different size
font2 = self.doc.get_or_create_font(
font_size=16,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create font with different color
font3 = self.doc.get_or_create_font(
font_size=14,
colour=(0, 255, 0),
weight=FontWeight.BOLD
)
# Create font with different weight
font4 = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.NORMAL
)
# All should be different objects
self.assertIsNot(font1, font2)
self.assertIsNot(font1, font3)
self.assertIsNot(font1, font4)
self.assertIsNot(font2, font3)
self.assertIsNot(font2, font4)
self.assertIsNot(font3, font4)
# Should have four fonts in registry
self.assertEqual(len(self.doc._fonts), 4)
def test_get_or_create_font_with_all_parameters(self):
"""Test get_or_create_font with all parameters."""
font = self.doc.get_or_create_font(
font_path="path/to/font.ttf",
font_size=18,
colour=(128, 64, 192),
weight=FontWeight.BOLD,
style=FontStyle.ITALIC,
decoration=TextDecoration.UNDERLINE,
background=(255, 255, 255, 128),
language="fr_FR",
min_hyphenation_width=80
)
self.assertEqual(font._font_path, "path/to/font.ttf")
self.assertEqual(font.font_size, 18)
self.assertEqual(font.colour, (128, 64, 192))
self.assertEqual(font.weight, FontWeight.BOLD)
self.assertEqual(font.style, FontStyle.ITALIC)
self.assertEqual(font.decoration, TextDecoration.UNDERLINE)
self.assertEqual(font.background, (255, 255, 255, 128))
self.assertEqual(font.language, "fr_FR")
self.assertEqual(font.min_hyphenation_width, 80)
def test_get_or_create_font_with_defaults(self):
"""Test get_or_create_font with default values."""
font = self.doc.get_or_create_font()
# Should create font with default values
self.assertIsNotNone(font)
self.assertEqual(font.font_size, 16) # Default font size
self.assertEqual(font.colour, (0, 0, 0)) # Default black color
self.assertEqual(font.weight, FontWeight.NORMAL)
self.assertEqual(font.style, FontStyle.NORMAL)
self.assertEqual(font.decoration, TextDecoration.NONE)
class TestChapterFontRegistry(unittest.TestCase):
"""Test cases for Chapter font registry functionality."""
def setUp(self):
"""Set up test fixtures."""
self.doc = Document("Test Document", "en-US")
self.chapter = Chapter("Test Chapter", 1, parent=self.doc)
def test_chapter_uses_parent_font_registry(self):
"""Test that chapter uses parent document's font registry."""
# Create font through chapter - should delegate to parent
font1 = self.chapter.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create same font through document - should return same object
font2 = self.doc.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Should be the same font object
self.assertIs(font1, font2)
# Should be stored in document's registry, not chapter's
self.assertEqual(len(self.doc._fonts), 1)
self.assertEqual(len(self.chapter._fonts), 0)
def test_chapter_without_parent_manages_own_fonts(self):
"""Test that chapter without parent manages its own fonts."""
# Create chapter without parent
standalone_chapter = Chapter("Standalone Chapter", 1)
# Create font through chapter
font1 = standalone_chapter.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create same font again - should reuse
font2 = standalone_chapter.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Should be the same font object
self.assertIs(font1, font2)
# Should be stored in chapter's own registry
self.assertEqual(len(standalone_chapter._fonts), 1)
def test_chapter_parent_assignment(self):
"""Test that chapter parent assignment works correctly."""
# Create chapter with parent
chapter_with_parent = Chapter("Chapter with Parent", 1, parent=self.doc)
self.assertEqual(chapter_with_parent._parent, self.doc)
# Create chapter without parent
chapter_without_parent = Chapter("Chapter without Parent", 1)
self.assertIsNone(chapter_without_parent._parent)
class TestBookFontRegistry(unittest.TestCase):
"""Test cases for Book font registry functionality."""
def setUp(self):
"""Set up test fixtures."""
self.book = Book("Test Book", "Author Name", "en-US")
def test_book_inherits_document_font_registry(self):
"""Test that Book inherits Document's font registry functionality."""
# Create font through book
font1 = self.book.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Create same font again - should reuse
font2 = self.book.get_or_create_font(
font_size=14,
colour=(255, 0, 0),
weight=FontWeight.BOLD
)
# Should be the same font object
self.assertIs(font1, font2)
# Should have one font in registry
self.assertEqual(len(self.book._fonts), 1)
if __name__ == '__main__':
unittest.main()