cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 617 lines 22 kB view raw
1package public 2 3import ( 4 "image" 5 "image/png" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/stormlightlabs/noteleaf/internal/shared" 12) 13 14func TestMarkdownConverter(t *testing.T) { 15 converter := NewMarkdownConverter() 16 17 t.Run("Conversion", func(t *testing.T) { 18 t.Run("converts heading to HeaderBlock", func(t *testing.T) { 19 markdown := "# Hello World" 20 blocks, err := converter.ToLeaflet(markdown) 21 shared.AssertNoError(t, err, "ToLeaflet should succeed") 22 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 23 24 header, ok := blocks[0].Block.(HeaderBlock) 25 shared.AssertTrue(t, ok, "block should be HeaderBlock") 26 shared.AssertEqual(t, TypeHeaderBlock, header.Type, "type should match") 27 shared.AssertEqual(t, 1, header.Level, "level should be 1") 28 shared.AssertEqual(t, "Hello World", header.Plaintext, "text should match") 29 }) 30 31 t.Run("converts multiple heading levels", func(t *testing.T) { 32 markdown := "## Level 2\n\n### Level 3\n\n###### Level 6" 33 blocks, err := converter.ToLeaflet(markdown) 34 shared.AssertNoError(t, err, "ToLeaflet should succeed") 35 shared.AssertEqual(t, 3, len(blocks), "should have 3 blocks") 36 37 h2 := blocks[0].Block.(HeaderBlock) 38 shared.AssertEqual(t, 2, h2.Level, "first heading level") 39 40 h3 := blocks[1].Block.(HeaderBlock) 41 shared.AssertEqual(t, 3, h3.Level, "second heading level") 42 43 h6 := blocks[2].Block.(HeaderBlock) 44 shared.AssertEqual(t, 6, h6.Level, "third heading level") 45 }) 46 47 t.Run("converts paragraph to TextBlock", func(t *testing.T) { 48 markdown := "This is a simple paragraph." 49 blocks, err := converter.ToLeaflet(markdown) 50 shared.AssertNoError(t, err, "ToLeaflet should succeed") 51 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 52 53 text, ok := blocks[0].Block.(TextBlock) 54 shared.AssertTrue(t, ok, "block should be TextBlock") 55 shared.AssertEqual(t, TypeTextBlock, text.Type, "type should match") 56 shared.AssertEqual(t, "This is a simple paragraph.", text.Plaintext, "text should match") 57 }) 58 59 t.Run("converts code block to CodeBlock", func(t *testing.T) { 60 markdown := "```go\nfunc main() {\n}\n```" 61 blocks, err := converter.ToLeaflet(markdown) 62 shared.AssertNoError(t, err, "ToLeaflet should succeed") 63 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 64 65 code, ok := blocks[0].Block.(CodeBlock) 66 shared.AssertTrue(t, ok, "block should be CodeBlock") 67 shared.AssertEqual(t, TypeCodeBlock, code.Type, "type should match") 68 shared.AssertEqual(t, "go", code.Language, "language should match") 69 shared.AssertTrue(t, strings.Contains(code.Plaintext, "func main"), "code content should match") 70 }) 71 72 t.Run("converts blockquote to BlockquoteBlock", func(t *testing.T) { 73 markdown := "> This is a quote" 74 blocks, err := converter.ToLeaflet(markdown) 75 shared.AssertNoError(t, err, "ToLeaflet should succeed") 76 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 77 78 quote, ok := blocks[0].Block.(BlockquoteBlock) 79 shared.AssertTrue(t, ok, "block should be BlockquoteBlock") 80 shared.AssertEqual(t, TypeBlockquoteBlock, quote.Type, "type should match") 81 shared.AssertTrue(t, strings.Contains(quote.Plaintext, "This is a quote"), "quote text should match") 82 }) 83 84 t.Run("converts list to UnorderedListBlock", func(t *testing.T) { 85 markdown := "- Item 1\n- Item 2\n- Item 3" 86 blocks, err := converter.ToLeaflet(markdown) 87 shared.AssertNoError(t, err, "ToLeaflet should succeed") 88 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 89 90 list, ok := blocks[0].Block.(UnorderedListBlock) 91 shared.AssertTrue(t, ok, "block should be UnorderedListBlock") 92 shared.AssertEqual(t, TypeUnorderedListBlock, list.Type, "type should match") 93 shared.AssertEqual(t, 3, len(list.Children), "should have 3 items") 94 95 item1 := list.Children[0].Content.(TextBlock) 96 shared.AssertTrue(t, strings.Contains(item1.Plaintext, "Item 1"), "first item text") 97 }) 98 99 t.Run("converts horizontal rule to HorizontalRuleBlock", func(t *testing.T) { 100 markdown := "---" 101 blocks, err := converter.ToLeaflet(markdown) 102 shared.AssertNoError(t, err, "ToLeaflet should succeed") 103 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 104 105 hr, ok := blocks[0].Block.(HorizontalRuleBlock) 106 shared.AssertTrue(t, ok, "block should be HorizontalRuleBlock") 107 shared.AssertEqual(t, TypeHorizontalRuleBlock, hr.Type, "type should match") 108 }) 109 110 t.Run("converts mixed blocks", func(t *testing.T) { 111 markdown := `# Title 112 113This is a paragraph. 114 115## Subtitle 116 117- List item 1 118- List item 2 119 120--- 121 122` + "```go\ncode\n```" 123 124 blocks, err := converter.ToLeaflet(markdown) 125 126 shared.AssertNoError(t, err, "ToLeaflet should succeed") 127 shared.AssertTrue(t, len(blocks) >= 5, "should have multiple blocks") 128 }) 129 }) 130 131 t.Run("Facets", func(t *testing.T) { 132 t.Run("extracts bold facet", func(t *testing.T) { 133 markdown := "This is **bold** text" 134 blocks, err := converter.ToLeaflet(markdown) 135 136 shared.AssertNoError(t, err, "ToLeaflet should succeed") 137 text := blocks[0].Block.(TextBlock) 138 139 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 140 shared.AssertTrue(t, strings.Contains(text.Plaintext, "bold"), "text should contain 'bold'") 141 }) 142 143 t.Run("extracts italic facet", func(t *testing.T) { 144 markdown := "This is *italic* text" 145 blocks, err := converter.ToLeaflet(markdown) 146 147 shared.AssertNoError(t, err, "ToLeaflet should succeed") 148 text := blocks[0].Block.(TextBlock) 149 150 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 151 }) 152 153 t.Run("extracts inline code facet", func(t *testing.T) { 154 markdown := "This is `code` text" 155 blocks, err := converter.ToLeaflet(markdown) 156 157 shared.AssertNoError(t, err, "ToLeaflet should succeed") 158 text := blocks[0].Block.(TextBlock) 159 160 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 161 shared.AssertTrue(t, strings.Contains(text.Plaintext, "code"), "text should contain 'code'") 162 }) 163 164 t.Run("extracts link facet", func(t *testing.T) { 165 markdown := "This is a [link](https://example.com)" 166 blocks, err := converter.ToLeaflet(markdown) 167 168 shared.AssertNoError(t, err, "ToLeaflet should succeed") 169 text := blocks[0].Block.(TextBlock) 170 171 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 172 shared.AssertTrue(t, strings.Contains(text.Plaintext, "link"), "text should contain 'link'") 173 174 foundLink := false 175 for _, facet := range text.Facets { 176 for _, feature := range facet.Features { 177 if link, ok := feature.(FacetLink); ok { 178 shared.AssertEqual(t, "https://example.com", link.URI, "link URI should match") 179 foundLink = true 180 } 181 } 182 } 183 shared.AssertTrue(t, foundLink, "should have found link facet") 184 }) 185 186 t.Run("extracts strikethrough facet", func(t *testing.T) { 187 markdown := "This is ~~deleted~~ text" 188 blocks, err := converter.ToLeaflet(markdown) 189 190 shared.AssertNoError(t, err, "ToLeaflet should succeed") 191 text := blocks[0].Block.(TextBlock) 192 193 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 194 }) 195 196 t.Run("extracts multiple facets", func(t *testing.T) { 197 markdown := "This has **bold** and *italic* and `code`" 198 blocks, err := converter.ToLeaflet(markdown) 199 200 shared.AssertNoError(t, err, "ToLeaflet should succeed") 201 text := blocks[0].Block.(TextBlock) 202 203 shared.AssertTrue(t, len(text.Facets) >= 3, "should have at least 3 facets") 204 }) 205 206 t.Run("handles overlapping bold and italic", func(t *testing.T) { 207 markdown := "***bold and italic***" 208 blocks, err := converter.ToLeaflet(markdown) 209 210 shared.AssertNoError(t, err, "ToLeaflet should succeed") 211 text := blocks[0].Block.(TextBlock) 212 213 shared.AssertEqual(t, "bold and italic", text.Plaintext, "text should be correct") 214 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 215 216 facet := text.Facets[0] 217 shared.AssertEqual(t, 2, len(facet.Features), "should have 2 features") 218 219 hasBold := false 220 hasItalic := false 221 for _, feature := range facet.Features { 222 switch feature.(type) { 223 case FacetBold: 224 hasBold = true 225 case FacetItalic: 226 hasItalic = true 227 } 228 } 229 shared.AssertTrue(t, hasBold, "should have bold feature") 230 shared.AssertTrue(t, hasItalic, "should have italic feature") 231 }) 232 233 t.Run("handles nested bold in italic", func(t *testing.T) { 234 markdown := "*italic **and bold** still italic*" 235 blocks, err := converter.ToLeaflet(markdown) 236 237 shared.AssertNoError(t, err, "ToLeaflet should succeed") 238 text := blocks[0].Block.(TextBlock) 239 240 shared.AssertEqual(t, "italic and bold still italic", text.Plaintext, "text should be correct") 241 shared.AssertTrue(t, len(text.Facets) >= 2, "should have multiple facets") 242 243 foundOverlap := false 244 for _, facet := range text.Facets { 245 if strings.Contains(text.Plaintext[facet.Index.ByteStart:facet.Index.ByteEnd], "and bold") { 246 shared.AssertTrue(t, len(facet.Features) >= 2, "overlapping section should have multiple features") 247 foundOverlap = true 248 } 249 } 250 shared.AssertTrue(t, foundOverlap, "should find overlapping facet") 251 }) 252 253 t.Run("handles link with formatting", func(t *testing.T) { 254 markdown := "[**bold link**](https://example.com)" 255 blocks, err := converter.ToLeaflet(markdown) 256 257 shared.AssertNoError(t, err, "ToLeaflet should succeed") 258 text := blocks[0].Block.(TextBlock) 259 260 shared.AssertEqual(t, "bold link", text.Plaintext, "text should be correct") 261 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 262 263 hasLink := false 264 hasBold := false 265 for _, facet := range text.Facets { 266 for _, feature := range facet.Features { 267 switch f := feature.(type) { 268 case FacetLink: 269 hasLink = true 270 shared.AssertEqual(t, "https://example.com", f.URI, "link URI should match") 271 case FacetBold: 272 hasBold = true 273 } 274 } 275 } 276 shared.AssertTrue(t, hasLink, "should have link feature") 277 shared.AssertTrue(t, hasBold, "should have bold feature") 278 }) 279 280 t.Run("handles strikethrough with bold", func(t *testing.T) { 281 markdown := "~~**deleted bold**~~" 282 blocks, err := converter.ToLeaflet(markdown) 283 284 shared.AssertNoError(t, err, "ToLeaflet should succeed") 285 text := blocks[0].Block.(TextBlock) 286 287 shared.AssertEqual(t, "deleted bold", text.Plaintext, "text should be correct") 288 shared.AssertTrue(t, len(text.Facets) > 0, "should have facets") 289 290 hasStrike := false 291 hasBold := false 292 for _, facet := range text.Facets { 293 for _, feature := range facet.Features { 294 switch feature.(type) { 295 case FacetStrikethrough: 296 hasStrike = true 297 case FacetBold: 298 hasBold = true 299 } 300 } 301 } 302 shared.AssertTrue(t, hasStrike, "should have strikethrough feature") 303 shared.AssertTrue(t, hasBold, "should have bold feature") 304 }) 305 306 t.Run("handles complex nested formatting", func(t *testing.T) { 307 markdown := "*italic **bold and italic** italic*" 308 blocks, err := converter.ToLeaflet(markdown) 309 310 shared.AssertNoError(t, err, "ToLeaflet should succeed") 311 text := blocks[0].Block.(TextBlock) 312 313 foundBoldItalic := false 314 for _, facet := range text.Facets { 315 content := text.Plaintext[facet.Index.ByteStart:facet.Index.ByteEnd] 316 if strings.Contains(content, "bold and italic") { 317 shared.AssertTrue(t, len(facet.Features) >= 2, "nested section should have multiple features") 318 foundBoldItalic = true 319 } 320 } 321 shared.AssertTrue(t, foundBoldItalic, "should find nested bold and italic section") 322 }) 323 }) 324 325 t.Run("Round-trip Conversion", func(t *testing.T) { 326 t.Run("heading round-trip", func(t *testing.T) { 327 original := "## Hello World" 328 blocks, err := converter.ToLeaflet(original) 329 shared.AssertNoError(t, err, "ToLeaflet should succeed") 330 331 markdown, err := converter.FromLeaflet(blocks) 332 shared.AssertNoError(t, err, "FromLeaflet should succeed") 333 shared.AssertTrue(t, strings.Contains(markdown, "Hello World"), "should contain original text") 334 shared.AssertTrue(t, strings.HasPrefix(markdown, "##"), "should have heading markers") 335 }) 336 337 t.Run("text round-trip", func(t *testing.T) { 338 original := "Simple paragraph" 339 blocks, err := converter.ToLeaflet(original) 340 shared.AssertNoError(t, err, "ToLeaflet should succeed") 341 342 markdown, err := converter.FromLeaflet(blocks) 343 shared.AssertNoError(t, err, "FromLeaflet should succeed") 344 shared.AssertTrue(t, strings.Contains(markdown, "Simple paragraph"), "should contain original text") 345 }) 346 347 t.Run("code block round-trip", func(t *testing.T) { 348 original := "```go\nfunc test() {}\n```" 349 blocks, err := converter.ToLeaflet(original) 350 shared.AssertNoError(t, err, "ToLeaflet should succeed") 351 352 markdown, err := converter.FromLeaflet(blocks) 353 shared.AssertNoError(t, err, "FromLeaflet should succeed") 354 shared.AssertTrue(t, strings.Contains(markdown, "```"), "should have code fences") 355 shared.AssertTrue(t, strings.Contains(markdown, "func test"), "should contain code") 356 }) 357 358 t.Run("list round-trip", func(t *testing.T) { 359 original := "- Item 1\n- Item 2" 360 blocks, err := converter.ToLeaflet(original) 361 shared.AssertNoError(t, err, "ToLeaflet should succeed") 362 363 markdown, err := converter.FromLeaflet(blocks) 364 shared.AssertNoError(t, err, "FromLeaflet should succeed") 365 366 shared.AssertTrue(t, strings.Contains(markdown, "Item 1"), "should contain first item") 367 shared.AssertTrue(t, strings.Contains(markdown, "Item 2"), "should contain second item") 368 shared.AssertTrue(t, strings.Contains(markdown, "-"), "should have list markers") 369 }) 370 }) 371 372 t.Run("Edge Cases", func(t *testing.T) { 373 t.Run("handles empty markdown", func(t *testing.T) { 374 blocks, err := converter.ToLeaflet("") 375 shared.AssertNoError(t, err, "should handle empty string") 376 shared.AssertEqual(t, 0, len(blocks), "should have no blocks") 377 }) 378 379 t.Run("skips empty paragraphs", func(t *testing.T) { 380 markdown := "\n\n\n" 381 blocks, err := converter.ToLeaflet(markdown) 382 shared.AssertNoError(t, err, "should succeed") 383 shared.AssertEqual(t, 0, len(blocks), "should skip empty paragraphs") 384 }) 385 386 t.Run("handles special characters", func(t *testing.T) { 387 markdown := "Text with *special* characters" 388 blocks, err := converter.ToLeaflet(markdown) 389 shared.AssertNoError(t, err, "should handle special characters") 390 shared.AssertEqual(t, 1, len(blocks), "should have 1 block") 391 392 text := blocks[0].Block.(TextBlock) 393 shared.AssertTrue(t, strings.Contains(text.Plaintext, "special"), "should preserve text") 394 shared.AssertTrue(t, strings.Contains(text.Plaintext, "characters"), "should preserve text") 395 }) 396 397 t.Run("handles multiple paragraphs", func(t *testing.T) { 398 markdown := "First paragraph\n\nSecond paragraph" 399 blocks, err := converter.ToLeaflet(markdown) 400 shared.AssertNoError(t, err, "should succeed") 401 shared.AssertEqual(t, 2, len(blocks), "should have 2 blocks") 402 }) 403 }) 404 405 t.Run("Image Handling", func(t *testing.T) { 406 tmpDir := t.TempDir() 407 createTestImage := func(t *testing.T, name string, width, height int) string { 408 path := filepath.Join(tmpDir, name) 409 410 img := image.NewRGBA(image.Rect(0, 0, width, height)) 411 f, err := os.Create(path) 412 shared.AssertNoError(t, err, "should create image file") 413 defer f.Close() 414 415 err = png.Encode(f, img) 416 shared.AssertNoError(t, err, "should encode image") 417 418 return path 419 } 420 421 t.Run("converts image without resolver (placeholder)", func(t *testing.T) { 422 markdown := "![alt text](image.png)" 423 converter := NewMarkdownConverter() 424 blocks, err := converter.ToLeaflet(markdown) 425 426 shared.AssertNoError(t, err, "ToLeaflet should succeed") 427 shared.AssertTrue(t, len(blocks) >= 1, "should have at least 1 block") 428 429 var imgBlock ImageBlock 430 found := false 431 for _, block := range blocks { 432 if img, ok := block.Block.(ImageBlock); ok { 433 imgBlock = img 434 found = true 435 break 436 } 437 } 438 439 shared.AssertTrue(t, found, "should find image block") 440 shared.AssertEqual(t, TypeImageBlock, imgBlock.Type, "type should match") 441 shared.AssertEqual(t, "alt text", imgBlock.Alt, "alt text should match") 442 shared.AssertEqual(t, "bafkreiplaceholder", imgBlock.Image.Ref.Link, "should have placeholder CID") 443 }) 444 445 t.Run("resolves local image with dimensions", func(t *testing.T) { 446 _ = createTestImage(t, "test.png", 800, 600) 447 markdown := "![test image](test.png)" 448 449 resolver := &LocalImageResolver{} 450 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 451 452 blocks, err := converter.ToLeaflet(markdown) 453 shared.AssertNoError(t, err, "ToLeaflet should succeed") 454 shared.AssertTrue(t, len(blocks) >= 1, "should have at least 1 block") 455 456 var imgBlock ImageBlock 457 found := false 458 for _, block := range blocks { 459 if img, ok := block.Block.(ImageBlock); ok { 460 imgBlock = img 461 found = true 462 break 463 } 464 } 465 466 shared.AssertTrue(t, found, "should find image block") 467 shared.AssertEqual(t, "test image", imgBlock.Alt, "alt text should match") 468 shared.AssertEqual(t, 800, imgBlock.AspectRatio.Width, "width should match") 469 shared.AssertEqual(t, 600, imgBlock.AspectRatio.Height, "height should match") 470 shared.AssertEqual(t, "image/png", imgBlock.Image.MimeType, "mime type should match") 471 }) 472 473 t.Run("handles inline images in paragraph", func(t *testing.T) { 474 _ = createTestImage(t, "inline.png", 100, 100) 475 markdown := "Some text before ![inline](inline.png) and text after" 476 477 resolver := &LocalImageResolver{} 478 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 479 480 blocks, err := converter.ToLeaflet(markdown) 481 shared.AssertNoError(t, err, "ToLeaflet should succeed") 482 shared.AssertTrue(t, len(blocks) >= 2, "should have multiple blocks for inline images") 483 484 textBlock1, ok := blocks[0].Block.(TextBlock) 485 shared.AssertTrue(t, ok, "first block should be text") 486 shared.AssertTrue(t, strings.Contains(textBlock1.Plaintext, "Some text before"), "should contain text before image") 487 488 imgBlock, ok := blocks[1].Block.(ImageBlock) 489 shared.AssertTrue(t, ok, "second block should be image") 490 shared.AssertEqual(t, "inline", imgBlock.Alt, "alt text should match") 491 }) 492 493 t.Run("handles multiple images", func(t *testing.T) { 494 _ = createTestImage(t, "img1.png", 200, 150) 495 _ = createTestImage(t, "img2.png", 300, 200) 496 markdown := "![first](img1.png)\n\n![second](img2.png)" 497 498 resolver := &LocalImageResolver{} 499 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 500 501 blocks, err := converter.ToLeaflet(markdown) 502 shared.AssertNoError(t, err, "ToLeaflet should succeed") 503 504 var imageBlocks []ImageBlock 505 for _, block := range blocks { 506 if img, ok := block.Block.(ImageBlock); ok { 507 imageBlocks = append(imageBlocks, img) 508 } 509 } 510 511 shared.AssertEqual(t, 2, len(imageBlocks), "should have 2 image blocks") 512 shared.AssertEqual(t, "first", imageBlocks[0].Alt, "first alt text") 513 shared.AssertEqual(t, 200, imageBlocks[0].AspectRatio.Width, "first width") 514 shared.AssertEqual(t, "second", imageBlocks[1].Alt, "second alt text") 515 shared.AssertEqual(t, 300, imageBlocks[1].AspectRatio.Width, "second width") 516 }) 517 518 t.Run("uses custom blob uploader", func(t *testing.T) { 519 _ = createTestImage(t, "upload.png", 100, 100) 520 markdown := "![uploaded](upload.png)" 521 522 uploadCalled := false 523 resolver := &LocalImageResolver{ 524 BlobUploader: func(data []byte, mimeType string) (Blob, error) { 525 uploadCalled = true 526 shared.AssertEqual(t, "image/png", mimeType, "mime type should be png") 527 shared.AssertTrue(t, len(data) > 0, "should have data") 528 529 return Blob{ 530 Type: TypeBlob, 531 Ref: CID{Link: "bafkreicustomcid"}, 532 MimeType: mimeType, 533 Size: len(data), 534 }, nil 535 }, 536 } 537 538 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 539 blocks, err := converter.ToLeaflet(markdown) 540 541 shared.AssertNoError(t, err, "ToLeaflet should succeed") 542 shared.AssertTrue(t, uploadCalled, "upload should be called") 543 544 imgBlock, ok := blocks[0].Block.(ImageBlock) 545 shared.AssertTrue(t, ok, "block should be image") 546 shared.AssertEqual(t, "bafkreicustomcid", imgBlock.Image.Ref.Link, "should use custom CID") 547 }) 548 549 t.Run("handles missing image gracefully", func(t *testing.T) { 550 markdown := "![missing](nonexistent.png)" 551 resolver := &LocalImageResolver{} 552 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 553 554 _, err := converter.ToLeaflet(markdown) 555 shared.AssertError(t, err, "should error on missing image") 556 shared.AssertTrue(t, strings.Contains(err.Error(), "failed to resolve image"), "error should mention resolution failure") 557 }) 558 559 t.Run("gathers images from complex document", func(t *testing.T) { 560 _ = createTestImage(t, "header.png", 100, 100) 561 _ = createTestImage(t, "body.png", 200, 200) 562 _ = createTestImage(t, "list.png", 50, 50) 563 564 markdown := `# Header 565 566![header image](header.png) 567 568Some text with ![inline](body.png) image. 569 570- List item 571- Another item with ![list img](list.png) 572` 573 574 resolver := &LocalImageResolver{} 575 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 576 577 blocks, err := converter.ToLeaflet(markdown) 578 shared.AssertNoError(t, err, "ToLeaflet should succeed") 579 580 imageCount := 0 581 for _, block := range blocks { 582 if _, ok := block.Block.(ImageBlock); ok { 583 imageCount++ 584 } 585 } 586 shared.AssertTrue(t, imageCount >= 2, "should find multiple images") 587 }) 588 589 t.Run("preserves image dimensions accurately", func(t *testing.T) { 590 testCases := []struct { 591 name string 592 width int 593 height int 594 }{ 595 {"square.png", 100, 100}, 596 {"landscape.png", 1920, 1080}, 597 {"portrait.png", 1080, 1920}, 598 {"wide.png", 2560, 1440}, 599 } 600 601 for _, tc := range testCases { 602 createTestImage(t, tc.name, tc.width, tc.height) 603 markdown := "![test](" + tc.name + ")" 604 605 resolver := &LocalImageResolver{} 606 converter := NewMarkdownConverter().WithImageResolver(resolver, tmpDir) 607 608 blocks, err := converter.ToLeaflet(markdown) 609 shared.AssertNoError(t, err, "should convert "+tc.name) 610 611 imgBlock := blocks[0].Block.(ImageBlock) 612 shared.AssertEqual(t, tc.width, imgBlock.AspectRatio.Width, tc.name+" width") 613 shared.AssertEqual(t, tc.height, imgBlock.AspectRatio.Height, tc.name+" height") 614 } 615 }) 616 }) 617}