From 88680f1954ff808f566ad878f651e29f2f134ce0 Mon Sep 17 00:00:00 2001 From: despiegk Date: Mon, 1 Dec 2025 19:53:51 +0100 Subject: [PATCH] ... --- lib/web/doctree/client/client.v | 133 +++++++++--------- lib/web/doctree/client/client_links.v | 119 ---------------- lib/web/doctree/client/client_test.v | 185 ++----------------------- lib/web/doctree/core/processor_test.v | 189 ++++++++++++++++++++++++-- 4 files changed, 253 insertions(+), 373 deletions(-) delete mode 100644 lib/web/doctree/client/client_links.v diff --git a/lib/web/doctree/client/client.v b/lib/web/doctree/client/client.v index 931d3de1..b62640ae 100644 --- a/lib/web/doctree/client/client.v +++ b/lib/web/doctree/client/client.v @@ -234,80 +234,81 @@ pub fn (mut c DocTreeClient) has_errors(collection_name string) bool { pub fn (mut c DocTreeClient) copy_collection(collection_name string, destination_path string) ! { // TODO: list over all pages, links & files and copy them to destination + } -// will copy all pages linked from a page to a destination directory as well as the page itself -pub fn (mut c DocTreeClient) copy_pages(collection_name string, page_name string, destination_path string) ! { - // TODO: copy page itself +// // will copy all pages linked from a page to a destination directory as well as the page itself +// pub fn (mut c DocTreeClient) copy_pages(collection_name string, page_name string, destination_path string) ! { +// // TODO: copy page itself - // Get page links from metadata - links := c.get_page_links(collection_name, page_name)! +// // Get page links from metadata +// links := c.get_page_links(collection_name, page_name)! - // Create img subdirectory - mut img_dest := pathlib.get_dir(path: '${destination_path}', create: true)! +// // Create img subdirectory +// mut img_dest := pathlib.get_dir(path: '${destination_path}', create: true)! - // Copy only image links - for link in links { - if link.file_type != .page { - continue - } - if link.status == .external { - continue - } - // Get image path and copy - img_path := c.get_page_path(link.target_collection_name, link.target_item_name)! - mut src := pathlib.get_file(path: img_path)! - src.copy(dest: '${img_dest.path}/${src.name_fix_no_ext()}')! - console.print_debug(' ********. Copied page: ${src.path} to ${img_dest.path}/${src.name_fix_no_ext()}') - } -} +// // Copy only image links +// for link in links { +// if link.file_type != .page { +// continue +// } +// if link.status == .external { +// continue +// } +// // Get image path and copy +// img_path := c.get_page_path(link.target_collection_name, link.target_item_name)! +// mut src := pathlib.get_file(path: img_path)! +// src.copy(dest: '${img_dest.path}/${src.name_fix_no_ext()}')! +// console.print_debug(' ********. Copied page: ${src.path} to ${img_dest.path}/${src.name_fix_no_ext()}') +// } +// } -pub fn (mut c DocTreeClient) copy_images(collection_name string, page_name string, destination_path string) ! { - // Get page links from metadata - links := c.get_page_links(collection_name, page_name)! +// pub fn (mut c DocTreeClient) copy_images(collection_name string, page_name string, destination_path string) ! { +// // Get page links from metadata +// links := c.get_page_links(collection_name, page_name)! - // Create img subdirectory - mut img_dest := pathlib.get_dir(path: '${destination_path}/img', create: true)! +// // Create img subdirectory +// mut img_dest := pathlib.get_dir(path: '${destination_path}/img', create: true)! - // Copy only image links - for link in links { - if link.file_type != .image { - continue - } - if link.status == .external { - continue - } - // Get image path and copy - img_path := c.get_image_path(link.target_collection_name, link.target_item_name)! - mut src := pathlib.get_file(path: img_path)! - src.copy(dest: '${img_dest.path}/${src.name_fix_no_ext()}')! - // console.print_debug('Copied image: ${src.path} to ${img_dest.path}/${src.name_fix()}') - } -} +// // Copy only image links +// for link in links { +// if link.file_type != .image { +// continue +// } +// if link.status == .external { +// continue +// } +// // Get image path and copy +// img_path := c.get_image_path(link.target_collection_name, link.target_item_name)! +// mut src := pathlib.get_file(path: img_path)! +// src.copy(dest: '${img_dest.path}/${src.name_fix_no_ext()}')! +// // console.print_debug('Copied image: ${src.path} to ${img_dest.path}/${src.name_fix()}') +// } +// } -// copy_files copies all non-image files from a page to a destination directory -// Files are placed in {destination}/files/ subdirectory -// Only copies files referenced in the page (via links) -pub fn (mut c DocTreeClient) copy_files(collection_name string, page_name string, destination_path string) ! { - // Get page links from metadata - links := c.get_page_links(collection_name, page_name)! +// // copy_files copies all non-image files from a page to a destination directory +// // Files are placed in {destination}/files/ subdirectory +// // Only copies files referenced in the page (via links) +// pub fn (mut c DocTreeClient) copy_files(collection_name string, page_name string, destination_path string) ! { +// // Get page links from metadata +// links := c.get_page_links(collection_name, page_name)! - // Create files subdirectory - mut files_dest := pathlib.get_dir(path: '${destination_path}/files', create: true)! +// // Create files subdirectory +// mut files_dest := pathlib.get_dir(path: '${destination_path}/files', create: true)! - // Copy only file links (non-image files) - for link in links { - if link.file_type != .file { - continue - } - if link.status == .external { - continue - } - // println(link) - // Get file path and copy - file_path := c.get_file_path(link.target_collection_name, link.target_item_name)! - mut src := pathlib.get_file(path: file_path)! - // src.copy(dest: '${files_dest.path}/${src.name_fix_no_ext()}')! - console.print_debug('Copied file: ${src.path} to ${files_dest.path}/${src.name_fix_no_ext()}') - } -} +// // Copy only file links (non-image files) +// for link in links { +// if link.file_type != .file { +// continue +// } +// if link.status == .external { +// continue +// } +// // println(link) +// // Get file path and copy +// file_path := c.get_file_path(link.target_collection_name, link.target_item_name)! +// mut src := pathlib.get_file(path: file_path)! +// // src.copy(dest: '${files_dest.path}/${src.name_fix_no_ext()}')! +// console.print_debug('Copied file: ${src.path} to ${files_dest.path}/${src.name_fix_no_ext()}') +// } +// } diff --git a/lib/web/doctree/client/client_links.v b/lib/web/doctree/client/client_links.v deleted file mode 100644 index 44748e65..00000000 --- a/lib/web/doctree/client/client_links.v +++ /dev/null @@ -1,119 +0,0 @@ -module client - -import incubaid.herolib.core.pathlib -import incubaid.herolib.core.texttools -import incubaid.herolib.ui.console -import os -import json -import incubaid.herolib.core.redisclient - -// get_page_links returns all links found in a page and pages linked to it (recursive) -// This includes transitive links through page-to-page references -// External links, files, and images do not recurse further -pub fn (mut c DocTreeClient) get_page_links(collection_name string, page_name string) ![]LinkMetadata { - mut visited := map[string]bool{} - mut all_links := []LinkMetadata{} - c.collect_page_links_recursive(collection_name, page_name, mut visited, mut all_links)! - return all_links -} - -// collect_page_links_recursive is the internal recursive implementation -// It traverses all linked pages and collects all links found -// -// Thread safety: Each call to get_page_links gets its own visited map -// Circular references are prevented by tracking visited pages -// -// Link types behavior: -// - .page links: Recursively traverse to get links from the target page -// - .file and .image links: Included in results but not recursively expanded -// - .external links: Included in results but not recursively expanded -fn (mut c DocTreeClient) collect_page_links_recursive(collection_name string, page_name string, mut visited map[string]bool, mut all_links []LinkMetadata) ! { - // Create unique key for cycle detection - page_key := '${collection_name}:${page_name}' - - // Prevent infinite loops on circular page references - // Example: Page A → Page B → Page A - if page_key in visited { - return - } - visited[page_key] = true - - // Get collection metadata - metadata := c.get_collection_metadata(collection_name)! - fixed_page_name := texttools.name_fix(page_name) - - // Find the page in metadata - if fixed_page_name !in metadata.pages { - return error('page_not_found: Page "${page_name}" not found in collection metadata, for collection: "${collection_name}"') - } - - page_meta := metadata.pages[fixed_page_name] - - // Add all direct links from this page to the result - // This includes: pages, files, images, and external links - all_links << page_meta.links - - // Recursively traverse only page-to-page links - for link in page_meta.links { - // Only recursively process links to other pages within the doctree - // Skip external links (http, https, mailto, etc.) - // Skip file and image links (these don't have "contained" links) - if link.file_type != .page || link.status == .external { - continue - } - - // Recursively collect links from the target page - c.collect_page_links_recursive(link.target_collection_name, link.target_item_name, mut - visited, mut all_links) or { - // If we encounter an error (e.g., target page doesn't exist in metadata), - // we continue processing other links rather than failing completely - // This provides graceful degradation for broken link references - continue - } - } -} - -// get_image_links returns all image links found in a page and related pages (recursive) -// This is a convenience function that filters get_page_links to only image links -pub fn (mut c DocTreeClient) get_image_links(collection_name string, page_name string) ![]LinkMetadata { - all_links := c.get_page_links(collection_name, page_name)! - mut image_links := []LinkMetadata{} - - for link in all_links { - if link.file_type == .image { - image_links << link - } - } - - return image_links -} - -// get_file_links returns all file links (non-image) found in a page and related pages (recursive) -// This is a convenience function that filters get_page_links to only file links -pub fn (mut c DocTreeClient) get_file_links(collection_name string, page_name string) ![]LinkMetadata { - all_links := c.get_page_links(collection_name, page_name)! - mut file_links := []LinkMetadata{} - - for link in all_links { - if link.file_type == .file { - file_links << link - } - } - - return file_links -} - -// get_page_link_targets returns all page-to-page link targets found in a page and related pages -// This is a convenience function that filters get_page_links to only page links -pub fn (mut c DocTreeClient) get_page_link_targets(collection_name string, page_name string) ![]LinkMetadata { - all_links := c.get_page_links(collection_name, page_name)! - mut page_links := []LinkMetadata{} - - for link in all_links { - if link.file_type == .page && link.status != .external { - page_links << link - } - } - - return page_links -} diff --git a/lib/web/doctree/client/client_test.v b/lib/web/doctree/client/client_test.v index 563917d8..267acc6e 100644 --- a/lib/web/doctree/client/client_test.v +++ b/lib/web/doctree/client/client_test.v @@ -54,28 +54,7 @@ fn setup_test_export() string { "name": "page2", "path": "", "collection_name": "testcollection", - "links": [ - { - "src": "logo.png", - "text": "logo", - "target": "logo.png", - "line": 3, - "target_collection_name": "testcollection", - "target_item_name": "logo.png", - "status": "ok", - "file_type": "image" - }, - { - "src": "data.csv", - "text": "data", - "target": "data.csv", - "line": 4, - "target_collection_name": "testcollection", - "target_item_name": "data.csv", - "status": "ok", - "file_type": "file" - } - ] + "links": [] } }, "files": { @@ -110,14 +89,7 @@ fn setup_test_export() string { } }, "files": {}, - "errors": [ - { - "category": "test", - "page_key": "intro", - "message": "Test error", - "line": 10 - } - ] + "errors": [] }' os.write_file(os.join_path(test_dir, 'meta', 'anothercollection.json'), metadata2) or { panic(err) @@ -455,23 +427,6 @@ fn test_list_pages_map() { assert pages_map['anothercollection'].len == 1 } -// Test list_markdown -fn test_list_markdown() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - markdown := client.list_markdown() or { panic(err) } - - assert markdown.contains('testcollection') - assert markdown.contains('anothercollection') - assert markdown.contains('page1') - assert markdown.contains('page2') - assert markdown.contains('intro') - assert markdown.contains('##') - assert markdown.contains('*') -} - // Test get_collection_metadata - success fn test_get_collection_metadata_success() { test_dir := setup_test_export() @@ -485,21 +440,6 @@ fn test_get_collection_metadata_success() { assert metadata.errors.len == 0 } -// Test get_collection_metadata - with errors -fn test_get_collection_metadata_with_errors() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - metadata := client.get_collection_metadata('anothercollection') or { panic(err) } - - assert metadata.name == 'anothercollection' - assert metadata.pages.len == 1 - assert metadata.errors.len == 1 - assert metadata.errors[0].message == 'Test error' - assert metadata.errors[0].line == 10 -} - // Test get_collection_metadata - not found fn test_get_collection_metadata_not_found() { test_dir := setup_test_export() @@ -513,78 +453,17 @@ fn test_get_collection_metadata_not_found() { assert false, 'Should have returned an error' } -// Test get_page_links - success -fn test_get_page_links_success() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - links := client.get_page_links('testcollection', 'page2') or { panic(err) } - - assert links.len == 2 - assert links[0].target_item_name == 'logo.png' - assert links[0].target_collection_name == 'testcollection' - assert links[0].file_type == .image -} - -// Test get_page_links - no links -fn test_get_page_links_empty() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - links := client.get_page_links('testcollection', 'page1') or { panic(err) } - - assert links.len == 0 -} - -// Test get_page_links - page not found -fn test_get_page_links_page_not_found() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - client.get_page_links('testcollection', 'nonexistent') or { - assert err.msg().contains('page_not_found') - return - } - assert false, 'Should have returned an error' -} - // Test get_collection_errors - success fn test_get_collection_errors_success() { test_dir := setup_test_export() defer { cleanup_test_export(test_dir) } - mut client := new(export_dir: test_dir) or { panic(err) } - errors := client.get_collection_errors('anothercollection') or { panic(err) } - - assert errors.len == 1 - assert errors[0].message == 'Test error' -} - -// Test get_collection_errors - no errors -fn test_get_collection_errors_empty() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - mut client := new(export_dir: test_dir) or { panic(err) } errors := client.get_collection_errors('testcollection') or { panic(err) } assert errors.len == 0 } -// Test has_errors - true -fn test_has_errors_true() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - has_errors := client.has_errors('anothercollection') - - assert has_errors == true -} - // Test has_errors - false fn test_has_errors_false() { test_dir := setup_test_export() @@ -596,7 +475,7 @@ fn test_has_errors_false() { assert has_errors == false } -// Test has_errors - collection not found +// Test has_errors - collection not found returns false fn test_has_errors_collection_not_found() { test_dir := setup_test_export() defer { cleanup_test_export(test_dir) } @@ -613,64 +492,16 @@ fn test_copy_images_success() { defer { cleanup_test_export(test_dir) } dest_dir := os.join_path(os.temp_dir(), 'copy_dest_${os.getpid()}') + defer { os.rmdir_all(dest_dir) or {} } + os.mkdir_all(dest_dir) or { panic(err) } - defer { cleanup_test_export(dest_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - client.copy_images('testcollection', 'page2', dest_dir) or { panic(err) } - - // Check that logo.png was copied to img subdirectory - assert os.exists(os.join_path(dest_dir, 'img', 'logo.png')) -} - -// Test copy_images - no images -fn test_copy_images_no_images() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - dest_dir := os.join_path(os.temp_dir(), 'copy_dest_empty_${os.getpid()}') - os.mkdir_all(dest_dir) or { panic(err) } - defer { cleanup_test_export(dest_dir) } mut client := new(export_dir: test_dir) or { panic(err) } client.copy_images('testcollection', 'page1', dest_dir) or { panic(err) } - // Should succeed even with no images - assert true -} - -// Test copy_files - success -fn test_copy_files_success() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - dest_dir := os.join_path(os.temp_dir(), 'copy_files_dest_${os.getpid()}') - os.mkdir_all(dest_dir) or { panic(err) } - defer { cleanup_test_export(dest_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - // Note: test data would need to be updated to have file links in page2 - // For now, this test demonstrates the pattern - client.copy_files('testcollection', 'page2', dest_dir) or { panic(err) } - - // Check that files were copied to files subdirectory - // assert os.exists(os.join_path(dest_dir, 'files', 'somefile.csv')) -} - -// Test copy_files - no files -fn test_copy_files_no_files() { - test_dir := setup_test_export() - defer { cleanup_test_export(test_dir) } - - dest_dir := os.join_path(os.temp_dir(), 'copy_files_empty_${os.getpid()}') - os.mkdir_all(dest_dir) or { panic(err) } - defer { cleanup_test_export(dest_dir) } - - mut client := new(export_dir: test_dir) or { panic(err) } - client.copy_files('testcollection', 'page1', dest_dir) or { panic(err) } - - // Should succeed even with no file links - assert true + // Check that images were copied to img subdirectory + assert os.exists(os.join_path(dest_dir, 'img', 'logo.png')) + assert os.exists(os.join_path(dest_dir, 'img', 'banner.jpg')) } // Test naming normalization edge cases diff --git a/lib/web/doctree/core/processor_test.v b/lib/web/doctree/core/processor_test.v index dea88610..78f0b4a3 100644 --- a/lib/web/doctree/core/processor_test.v +++ b/lib/web/doctree/core/processor_test.v @@ -6,22 +6,29 @@ import json const test_base = '/tmp/doctree_test' -fn testsuite_begin() { +// Clean up before and after each test +fn setup_test() { os.rmdir_all(test_base) or {} - os.mkdir_all(test_base)! + os.mkdir_all(test_base) or {} } -fn testsuite_end() { +fn cleanup_test() { os.rmdir_all(test_base) or {} } fn test_create_doctree() { + setup_test() + defer { cleanup_test() } + mut a := new(name: 'test_doctree')! assert a.name == 'test_doctree' assert a.collections.len == 0 } fn test_add_collection() { + setup_test() + defer { cleanup_test() } + // Create test collection col_path := '${test_base}/col1' os.mkdir_all(col_path)! @@ -39,6 +46,9 @@ fn test_add_collection() { } fn test_scan() { + setup_test() + defer { cleanup_test() } + // Create test structure os.mkdir_all('${test_base}/docs/guides')! mut cfile := pathlib.get_file(path: '${test_base}/docs/guides/.collection', create: true)! @@ -56,6 +66,9 @@ fn test_scan() { } fn test_export() { + setup_test() + defer { cleanup_test() } + // Setup col_path := '${test_base}/source/col1' export_path := '${test_base}/export' @@ -77,6 +90,9 @@ fn test_export() { } fn test_export_with_includes() { + setup_test() + defer { cleanup_test() } + // Setup: Create pages with includes col_path := '${test_base}/include_test' os.mkdir_all(col_path)! @@ -96,7 +112,7 @@ fn test_export_with_includes() { a.add_collection(mut pathlib.get_dir(path: col_path)!)! export_path := '${test_base}/export_include' - a.export(destination: export_path, include: true)! + a.export(destination: export_path, include: true, redis: false)! // Verify exported page1 has page2 content included exported := os.read_file('${export_path}/content/test_col/page1.md')! @@ -106,6 +122,9 @@ fn test_export_with_includes() { } fn test_export_without_includes() { + setup_test() + defer { cleanup_test() } + col_path := '${test_base}/no_include_test' os.mkdir_all(col_path)! @@ -119,7 +138,7 @@ fn test_export_without_includes() { a.add_collection(mut pathlib.get_dir(path: col_path)!)! export_path := '${test_base}/export_no_include' - a.export(destination: export_path, include: false)! + a.export(destination: export_path, include: false, redis: false)! // Verify exported page1 still has include action exported := os.read_file('${export_path}/content/test_col2/page1.md')! @@ -127,18 +146,28 @@ fn test_export_without_includes() { } fn test_error_deduplication() { + setup_test() + defer { cleanup_test() } + mut a := new(name: 'test')! col_path := '${test_base}/err_dedup_col' os.mkdir_all(col_path)! mut cfile := pathlib.get_file(path: '${col_path}/.collection', create: true)! cfile.write('name:err_dedup_col')! mut col := a.add_collection(mut pathlib.get_dir(path: col_path)!)! + assert col.name == 'err_dedup_col' // Ensure collection is added correctly } fn test_error_hash() { + setup_test() + defer { cleanup_test() } + // This test had no content, leaving it as a placeholder. } fn test_find_links() { + setup_test() + defer { cleanup_test() } + col_path := '${test_base}/find_links_test' os.mkdir_all(col_path)! @@ -160,6 +189,9 @@ fn test_find_links() { // Test with a valid link to ensure no errors are reported fn test_find_links_valid_link() { + setup_test() + defer { cleanup_test() } + // Setup col_path := '${test_base}/link_test' os.mkdir_all(col_path)! @@ -182,10 +214,13 @@ fn test_find_links_valid_link() { col := a.get_collection('test_col')! assert col.errors.len == 0 - a.export(destination: '${test_base}/export_links')! + a.export(destination: '${test_base}/export_links', redis: false)! } fn test_validate_broken_links() { + setup_test() + defer { cleanup_test() } + // Setup col_path := '${test_base}/broken_link_test' os.mkdir_all(col_path)! @@ -201,13 +236,17 @@ fn test_validate_broken_links() { a.add_collection(mut pathlib.get_dir(path: col_path)!)! // Validate - a.export(destination: '${test_base}/validate_broken_links')! + a.export(destination: '${test_base}/validate_broken_links', redis: false)! // Should have error col := a.get_collection('test_col')! + assert col.errors.len > 0 } fn test_fix_links() { + setup_test() + defer { cleanup_test() } + // Setup - all pages in same directory for simpler test col_path := '${test_base}/fix_link_test' os.mkdir_all(col_path)! @@ -230,20 +269,22 @@ fn test_fix_links() { mut p := col.page_get('page1')! original := p.content()! - println('Original: ${original}') + assert original.contains('[Link](page2)') fixed := p.content_with_fixed_links(FixLinksArgs{ include: true cross_collection: true export_mode: false })! - println('Fixed: ${fixed}') // The fix_links should work on content assert fixed.contains('[Link](page2.md)') } fn test_link_formats() { + setup_test() + defer { cleanup_test() } + col_path := '${test_base}/link_format_test' os.mkdir_all(col_path)! @@ -269,6 +310,9 @@ fn test_link_formats() { } fn test_cross_collection_links() { + setup_test() + defer { cleanup_test() } + // Setup two collections col1_path := '${test_base}/col1_cross' col2_path := '${test_base}/col2_cross' @@ -297,13 +341,16 @@ fn test_cross_collection_links() { col1 := a.get_collection('col1')! assert col1.errors.len == 0 - a.export(destination: '${test_base}/export_cross')! + a.export(destination: '${test_base}/export_cross', redis: false)! fixed := page1.read()! assert fixed.contains('[Link to col2](col2:page2)') // Unchanged } fn test_save_and_load() { + setup_test() + defer { cleanup_test() } + // Setup col_path := '${test_base}/save_test' os.mkdir_all(col_path)! @@ -318,9 +365,13 @@ fn test_save_and_load() { mut a := new(name: 'test')! a.add_collection(mut pathlib.get_dir(path: col_path)!)! col := a.get_collection('test_col')! + assert col.name == 'test_col' } fn test_save_with_errors() { + setup_test() + defer { cleanup_test() } + col_path := '${test_base}/error_save_test' os.mkdir_all(col_path)! @@ -329,9 +380,13 @@ fn test_save_with_errors() { mut a := new(name: 'test')! mut col := a.add_collection(mut pathlib.get_dir(path: col_path)!)! + assert col.name == 'err_col' // Ensure collection is added correctly } fn test_load_from_directory() { + setup_test() + defer { cleanup_test() } + // Setup multiple collections col1_path := '${test_base}/load_dir/col1' col2_path := '${test_base}/load_dir/col2' @@ -355,9 +410,14 @@ fn test_load_from_directory() { mut a := new(name: 'test')! a.add_collection(mut pathlib.get_dir(path: col1_path)!)! a.add_collection(mut pathlib.get_dir(path: col2_path)!)! + + assert a.collections.len == 2 } fn test_get_edit_url() { + setup_test() + defer { cleanup_test() } + // Create a mock collection mut doctree := new(name: 'test_doctree')! col_path := '${test_base}/git_test' @@ -373,5 +433,112 @@ fn test_get_edit_url() { // Get the page and collection edit URLs page := col.page_get('test_page')! - + // No asserts in original, adding one for completeness + assert page.name == 'test_page' +} + +fn test_export_recursive_links() { + setup_test() + defer { cleanup_test() } + + // Create 3 collections with chained links + col_a_path := '${test_base}/recursive_export/col_a' + col_b_path := '${test_base}/recursive_export/col_b' + col_c_path := '${test_base}/recursive_export/col_c' + + os.mkdir_all(col_a_path)! + os.mkdir_all(col_b_path)! + os.mkdir_all(col_c_path)! + + // Collection A: links to B + mut cfile_a := pathlib.get_file(path: '${col_a_path}/.collection', create: true)! + cfile_a.write('name:col_a')! + mut page_a := pathlib.get_file(path: '${col_a_path}/page_a.md', create: true)! + page_a.write('# Page A\n\nThis is page A.\n\n[Link to Page B](col_b:page_b)')! + + // Collection B: links to C + mut cfile_b := pathlib.get_file(path: '${col_b_path}/.collection', create: true)! + cfile_b.write('name:col_b')! + mut page_b := pathlib.get_file(path: '${col_b_path}/page_b.md', create: true)! + page_b.write('# Page B\n\nThis is page B with link to C.\n\n[Link to Page C](col_c:page_c)')! + + // Collection C: final page + mut cfile_c := pathlib.get_file(path: '${col_c_path}/.collection', create: true)! + cfile_c.write('name:col_c')! + mut page_c := pathlib.get_file(path: '${col_c_path}/page_c.md', create: true)! + page_c.write('# Page C\n\nThis is the final page in the chain.')! + + // Create DocTree and add all collections + mut a := new()! + a.add_collection(mut pathlib.get_dir(path: col_a_path)!)! + a.add_collection(mut pathlib.get_dir(path: col_b_path)!)! + a.add_collection(mut pathlib.get_dir(path: col_c_path)!)! + + // Export + export_path := '${test_base}/export_recursive' + a.export(destination: export_path, redis: false)! + + // Verify directory structure exists + assert os.exists('${export_path}/content'), 'Export content directory should exist' + assert os.exists('${export_path}/content/col_a'), 'Collection col_a directory should exist' + assert os.exists('${export_path}/meta'), 'Export meta directory should exist' + + // Verify all pages exist in col_a export directory + assert os.exists('${export_path}/content/col_a/page_a.md'), 'page_a.md should be exported' + assert os.exists('${export_path}/content/col_a/page_b.md'), 'page_b.md from col_b should be included' + assert os.exists('${export_path}/content/col_a/page_c.md'), 'page_c.md from col_c should be included' + + // Verify metadata files exist + assert os.exists('${export_path}/meta/col_a.json'), 'col_a metadata should exist' + assert os.exists('${export_path}/meta/col_b.json'), 'col_b metadata should exist' + assert os.exists('${export_path}/meta/col_c.json'), 'col_c metadata should exist' +} + +fn test_export_recursive_with_images() { + setup_test() + defer { cleanup_test() } + + col_a_path := '${test_base}/recursive_img/col_a' + col_b_path := '${test_base}/recursive_img/col_b' + + os.mkdir_all(col_a_path)! + os.mkdir_all(col_b_path)! + os.mkdir_all('${col_a_path}/img')! + os.mkdir_all('${col_b_path}/img')! + + // Collection A with local image + mut cfile_a := pathlib.get_file(path: '${col_a_path}/.collection', create: true)! + cfile_a.write('name:col_a')! + + mut page_a := pathlib.get_file(path: '${col_a_path}/page_a.md', create: true)! + page_a.write('# Page A\n\n![Local Image](local.png)\n\n[Link to B](col_b:page_b)')! + + // Create local image + os.write_file('${col_a_path}/img/local.png', 'fake png data')! + + // Collection B with image and linked page + mut cfile_b := pathlib.get_file(path: '${col_b_path}/.collection', create: true)! + cfile_b.write('name:col_b')! + + mut page_b := pathlib.get_file(path: '${col_b_path}/page_b.md', create: true)! + page_b.write('# Page B\n\n![B Image](b_image.jpg)')! + + // Create image in collection B + os.write_file('${col_b_path}/img/b_image.jpg', 'fake jpg data')! + + // Create DocTree + mut a := new()! + a.add_collection(mut pathlib.get_dir(path: col_a_path)!)! + a.add_collection(mut pathlib.get_dir(path: col_b_path)!)! + + export_path := '${test_base}/export_recursive_img' + a.export(destination: export_path, redis: false)! + + // Verify pages exported + assert os.exists('${export_path}/content/col_a/page_a.md'), 'page_a should exist' + assert os.exists('${export_path}/content/col_a/page_b.md'), 'page_b from col_b should be included' + + // Verify images exported to col_a image directory + assert os.exists('${export_path}/content/col_a/img/local.png'), 'Local image should exist' + assert os.exists('${export_path}/content/col_a/img/b_image.jpg'), 'Image from cross-collection reference should be copied' }