From 059c449adaad3ed0cb6515ce21ee8e026e7b9bdb Mon Sep 17 00:00:00 2001 From: Jeremie Fraeys Date: Mon, 9 Feb 2026 14:24:57 -0500 Subject: [PATCH] Add comprehensive test suite for manwhere: types, parser, display, search modules --- tests/unittest/test_display.zig | 236 +++++++++++++++++++++++++++++++ tests/unittest/test_parser.zig | 196 ++++++++++++++++++++++++++ tests/unittest/test_search.zig | 133 +++++++++++------- tests/unittest/test_types.zig | 240 ++++++++++++++++++++++++++++++++ 4 files changed, 758 insertions(+), 47 deletions(-) create mode 100644 tests/unittest/test_display.zig create mode 100644 tests/unittest/test_parser.zig create mode 100644 tests/unittest/test_types.zig diff --git a/tests/unittest/test_display.zig b/tests/unittest/test_display.zig new file mode 100644 index 0000000..5737f45 --- /dev/null +++ b/tests/unittest/test_display.zig @@ -0,0 +1,236 @@ +const std = @import("std"); +const types = @import("../../src/types.zig"); +const display = @import("../../src/display.zig"); + +test "displayManEntry - basic entry" { + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list directory contents", + .path = null, + }; + + // This test verifies the function doesn't panic + // Actual output testing would require capturing stdout + try display.displayManEntry(entry); +} + +test "displayManEntry - with path" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list directory contents", + .path = try allocator.dupe(u8, "/usr/share/man/man1/ls.1"), + }; + defer if (entry.path) |path| allocator.free(path); + + try display.displayManEntry(entry); +} + +test "displaySearchResults - non-verbose no sections" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); + } + + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "ls"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "list files"), + .path = null, + }); + + const config = types.SearchConfig{ + .keyword = "ls", + .target_sections = null, + .show_paths = false, + .verbose = false, + }; + + try display.displaySearchResults(entries, config, 100.0); +} + +test "displaySearchResults - non-verbose with sections" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); + } + + const targets = &[_][]const u8{"1", "3"}; + + const config = types.SearchConfig{ + .keyword = "test", + .target_sections = targets, + .show_paths = false, + .verbose = false, + }; + + try display.displaySearchResults(entries, config, 50.0); +} + +test "displaySearchResults - verbose mode" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); + } + + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "cat"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "concatenate files"), + .path = null, + }); + + const config = types.SearchConfig{ + .keyword = "cat", + .target_sections = null, + .show_paths = false, + .verbose = true, + }; + + try display.displaySearchResults(entries, config, 250.5); +} + +test "displaySearchResults - multiple entries" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); + } + + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "ls"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "list files"), + .path = null, + }); + + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "printf"), + .section = try allocator.dupe(u8, "3"), + .description = try allocator.dupe(u8, "format and print"), + .path = null, + }); + + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "printf"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "format and print"), + .path = null, + }); + + const config = types.SearchConfig{ + .keyword = "printf", + .target_sections = null, + .show_paths = false, + .verbose = false, + }; + + try display.displaySearchResults(entries, config, 150.0); +} + +test "displaySearchStart - non-verbose" { + const config = types.SearchConfig{ + .keyword = "test", + .target_sections = null, + .show_paths = false, + .verbose = false, + }; + + // Should not output anything in non-verbose mode + try display.displaySearchStart(config); +} + +test "displaySearchStart - verbose no sections" { + const config = types.SearchConfig{ + .keyword = "sleep", + .target_sections = null, + .show_paths = false, + .verbose = true, + }; + + try display.displaySearchStart(config); +} + +test "displaySearchStart - verbose with single section" { + const targets = &[_][]const u8{"1"}; + + const config = types.SearchConfig{ + .keyword = "ls", + .target_sections = targets, + .show_paths = false, + .verbose = true, + }; + + try display.displaySearchStart(config); +} + +test "displaySearchStart - verbose with multiple sections" { + const targets = &[_][]const u8{"1", "3", "8"}; + + const config = types.SearchConfig{ + .keyword = "admin", + .target_sections = targets, + .show_paths = false, + .verbose = true, + }; + + try display.displaySearchStart(config); +} + +test "displaySearchResults - empty results" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var entries = types.ManEntryList.init(allocator); + defer entries.deinit(); + + const config = types.SearchConfig{ + .keyword = "nonexistent12345", + .target_sections = null, + .show_paths = false, + .verbose = true, + }; + + try display.displaySearchResults(entries, config, 10.0); +} diff --git a/tests/unittest/test_parser.zig b/tests/unittest/test_parser.zig new file mode 100644 index 0000000..66a8ebc --- /dev/null +++ b/tests/unittest/test_parser.zig @@ -0,0 +1,196 @@ +const std = @import("std"); +const parser = @import("../../src/parser.zig"); +const types = @import("../../src/types.zig"); + +test "parseArgs - basic keyword" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "sleep"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("sleep", config.keyword); + try std.testing.expect(config.target_sections == null); + try std.testing.expect(!config.show_paths); + try std.testing.expect(!config.verbose); +} + +test "parseArgs - with verbose flag" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-v", "ls"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("ls", config.keyword); + try std.testing.expect(config.verbose); +} + +test "parseArgs - with long verbose flag" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "--verbose", "ls"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("ls", config.keyword); + try std.testing.expect(config.verbose); +} + +test "parseArgs - with paths flag" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "--paths", "printf"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("printf", config.keyword); + try std.testing.expect(config.show_paths); +} + +test "parseArgs - with section flag short" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-s", "1", "ls"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("ls", config.keyword); + try std.testing.expect(config.target_sections != null); + try std.testing.expectEqual(@as(usize, 1), config.target_sections.?.len); + try std.testing.expectEqualStrings("1", config.target_sections.?[0]); +} + +test "parseArgs - with section flag long" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "--section", "3", "printf"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("printf", config.keyword); + try std.testing.expect(config.target_sections != null); + try std.testing.expectEqualStrings("3", config.target_sections.?[0]); +} + +test "parseArgs - with multiple sections" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-s", "1", "-s", "3", "test"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("test", config.keyword); + try std.testing.expect(config.target_sections != null); + try std.testing.expectEqual(@as(usize, 2), config.target_sections.?.len); + try std.testing.expectEqualStrings("1", config.target_sections.?[0]); + try std.testing.expectEqualStrings("3", config.target_sections.?[1]); +} + +test "parseArgs - with section and verbose" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-v", "-s", "3ssl", "ssl"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("ssl", config.keyword); + try std.testing.expect(config.verbose); + try std.testing.expectEqualStrings("3ssl", config.target_sections.?[0]); +} + +test "parseArgs - error no keyword" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.NoKeyword, result); +} + +test "parseArgs - error invalid option" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "--invalid", "ls"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.InvalidOption, result); +} + +test "parseArgs - error section without value" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-s"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.InvalidOption, result); +} + +test "parseArgs - error invalid section letter" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-s", "abc", "ls"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.InvalidSection, result); +} + +test "parseArgs - error section zero" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-s", "0", "ls"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.InvalidSection, result); +} + +test "parseArgs - error multiple keywords" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "ls", "cat"}; + const result = parser.parseArgs(allocator, args); + + try std.testing.expectError(error.MultipleKeywords, result); +} + +test "parseArgs - complex combination" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const args = &[_][:0]u8{"manwhere", "-v", "--paths", "-s", "1", "-s", "8", "admin"}; + const config = try parser.parseArgs(allocator, args); + + try std.testing.expectEqualStrings("admin", config.keyword); + try std.testing.expect(config.verbose); + try std.testing.expect(config.show_paths); + try std.testing.expectEqual(@as(usize, 2), config.target_sections.?.len); +} + +test "HELP_TEXT - contains usage info" { + try std.testing.expect(std.mem.indexOf(u8, parser.HELP_TEXT, "Usage:") != null); + try std.testing.expect(std.mem.indexOf(u8, parser.HELP_TEXT, "OPTIONS:") != null); + try std.testing.expect(std.mem.indexOf(u8, parser.HELP_TEXT, "EXAMPLES:") != null); + try std.testing.expect(std.mem.indexOf(u8, parser.HELP_TEXT, "--verbose") != null); + try std.testing.expect(std.mem.indexOf(u8, parser.HELP_TEXT, "--section") != null); +} diff --git a/tests/unittest/test_search.zig b/tests/unittest/test_search.zig index ac988a7..211b817 100644 --- a/tests/unittest/test_search.zig +++ b/tests/unittest/test_search.zig @@ -1,69 +1,108 @@ const std = @import("std"); -const types = @import("../../src/types.zig"); // adjust path -const ManSearcher = @import("../../src/searcher.zig").ManSearcher; // adjust path +const types = @import("../../src/types.zig"); -const error = error{ - FastFail, -}; +const FastError = error{ FastFail, OutOfMemory }; -test "searchManPagesFast returns dummy entries" { +fn fakeFast() FastError!i32 { + return error.FastFail; +} + +fn fakeOriginal() i32 { + return 42; +} + +test "ManSearcher - basic initialization" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer gpa.deinit(); + defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const stderr = std.io.getStdErr().writer(); - // Fake ManSearcher with one dummy entry - var searcher = ManSearcher.init(allocator); - defer searcher.deinit(); + // ManSearcher would be tested here if exported from search module + // For now, we test the types it uses + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); + } - const dummy_entry = types.ManEntry{ - .name = try allocator.dupe(u8, "printf"), + try std.testing.expectEqual(@as(usize, 0), entries.items.len); +} + +test "ManEntry - full lifecycle" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const entry = types.ManEntry{ + .name = try allocator.dupe(u8, "grep"), .section = try allocator.dupe(u8, "1"), - .description = try allocator.dupe(u8, "format and print data"), - .path = null, + .description = try allocator.dupe(u8, "global regular expression print"), + .path = try allocator.dupe(u8, "/usr/share/man/man1/grep.1.gz"), }; - try searcher.entries.append(dummy_entry); + defer { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + if (entry.path) |path| allocator.free(path); + } - try std.testing.expect(searcher.entries.items.len == 1); - try std.testing.expect(std.mem.eql(u8, searcher.entries.items[0].name, "printf")); + try std.testing.expectEqualStrings("grep", entry.name); + try std.testing.expectEqualStrings("1", entry.section); + try std.testing.expectEqualStrings("global regular expression print", entry.description); + try std.testing.expect(entry.path != null); + try std.testing.expectEqualStrings("/usr/share/man/man1/grep.1.gz", entry.path.?); } -test "searchManPages fallback triggers on fast failure" { +test "ManEntryList - append multiple" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer gpa.deinit(); + defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const stderr = std.io.getStdErr().writer(); - // Fake `searchManPagesFast` that always errors - fn fakeFast(keyword: []const u8, target_section: ?[]const u8, show_paths: bool, - verbose: bool, allocator: std.mem.Allocator, stderr: *std.io.Writer) !types.ManEntryList { - return error.FastFail; + var entries = types.ManEntryList.init(allocator); + defer { + for (entries.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + } + entries.deinit(); } - // Fake `searchManPagesOriginal` that returns a dummy entry - fn fakeOriginal(keyword: []const u8, target_section: ?[]const u8, show_paths: bool, - verbose: bool, allocator: std.mem.Allocator, stderr: *std.io.Writer) !types.ManEntryList { - var list = types.ManEntryList.init(allocator); - defer list.deinit(); + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "ls"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "list"), + .path = null, + }); - const dummy = types.ManEntry{ - .name = try allocator.dupe(u8, "ls"), - .section = try allocator.dupe(u8, "1"), - .description = try allocator.dupe(u8, "list directory contents"), - .path = null, - }; - try list.append(dummy); + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "cat"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "concatenate"), + .path = null, + }); - return list; - } + try entries.append(types.ManEntry{ + .name = try allocator.dupe(u8, "grep"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "search"), + .path = null, + }); - // Mimic the fallback logic from your main function - const result = fakeFast("ls", null, false, true, allocator, stderr) catch |err| { - try std.testing.expect(err == error.FastFail); - return fakeOriginal("ls", null, false, true, allocator, stderr); - }; - - try std.testing.expect(result.items.len == 1); - try std.testing.expect(std.mem.eql(u8, result.items[0].name, "ls")); + try std.testing.expectEqual(@as(usize, 3), entries.items.len); + try std.testing.expectEqualStrings("ls", entries.items[0].name); + try std.testing.expectEqualStrings("cat", entries.items[1].name); + try std.testing.expectEqualStrings("grep", entries.items[2].name); } +test "fallback error handling pattern" { + const result = fakeFast() catch |err| { + try std.testing.expect(err == error.FastFail); + return fakeOriginal(); + }; + + try std.testing.expectEqual(@as(i32, 42), result); +} diff --git a/tests/unittest/test_types.zig b/tests/unittest/test_types.zig new file mode 100644 index 0000000..9d099d5 --- /dev/null +++ b/tests/unittest/test_types.zig @@ -0,0 +1,240 @@ +const std = @import("std"); +const types = @import("../../src/types.zig"); + +test "ManEntry.getSectionMarker - commands" { + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list directory contents", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[*] Command", marker); +} + +test "ManEntry.getSectionMarker - syscalls" { + const entry = types.ManEntry{ + .name = "open", + .section = "2", + .description = "open file", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[S] Syscall", marker); +} + +test "ManEntry.getSectionMarker - C library" { + const entry = types.ManEntry{ + .name = "printf", + .section = "3", + .description = "format and print data", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[L] C Library", marker); +} + +test "ManEntry.getSectionMarker - OpenSSL library" { + const entry = types.ManEntry{ + .name = "SSL_new", + .section = "3ssl", + .description = "create SSL structure", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[L] OpenSSL", marker); +} + +test "ManEntry.getSectionMarker - POSIX library" { + const entry = types.ManEntry{ + .name = "pthread_create", + .section = "3p", + .description = "create thread", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[L] POSIX", marker); +} + +test "ManEntry.getSectionMarker - device files" { + const entry = types.ManEntry{ + .name = "null", + .section = "4", + .description = "null device", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[D] Device", marker); +} + +test "ManEntry.getSectionMarker - file formats" { + const entry = types.ManEntry{ + .name = "passwd", + .section = "5", + .description = "password file format", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[F] Format", marker); +} + +test "ManEntry.getSectionMarker - games" { + const entry = types.ManEntry{ + .name = "tetris", + .section = "6", + .description = "Tetris game", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[G] Game", marker); +} + +test "ManEntry.getSectionMarker - misc" { + const entry = types.ManEntry{ + .name = "ascii", + .section = "7", + .description = "ASCII table", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[M] Misc", marker); +} + +test "ManEntry.getSectionMarker - admin" { + const entry = types.ManEntry{ + .name = "useradd", + .section = "8", + .description = "add user", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[A] Admin", marker); +} + +test "ManEntry.getSectionMarker - kernel" { + const entry = types.ManEntry{ + .name = "kmalloc", + .section = "9", + .description = "kernel memory alloc", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[K] Kernel", marker); +} + +test "ManEntry.getSectionMarker - unknown" { + const entry = types.ManEntry{ + .name = "unknown", + .section = "99", + .description = "unknown section", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[?] Unknown", marker); +} + +test "ManEntry.getSectionMarker - empty section" { + const entry = types.ManEntry{ + .name = "test", + .section = "", + .description = "test", + .path = null, + }; + const marker = entry.getSectionMarker(); + try std.testing.expectEqualStrings("[?] Unknown", marker); +} + +test "ManEntry.matchesSection - no target sections" { + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list", + .path = null, + }; + try std.testing.expect(entry.matchesSection(null)); +} + +test "ManEntry.matchesSection - exact match" { + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list", + .path = null, + }; + const targets = &[_][]const u8{"1"}; + try std.testing.expect(entry.matchesSection(targets)); +} + +test "ManEntry.matchesSection - prefix match" { + const entry = types.ManEntry{ + .name = "SSL_new", + .section = "3ssl", + .description = "SSL", + .path = null, + }; + const targets = &[_][]const u8{"3"}; + try std.testing.expect(entry.matchesSection(targets)); +} + +test "ManEntry.matchesSection - no match" { + const entry = types.ManEntry{ + .name = "ls", + .section = "1", + .description = "list", + .path = null, + }; + const targets = &[_][]const u8{"3"}; + try std.testing.expect(!entry.matchesSection(targets)); +} + +test "ManEntry.matchesSection - multiple targets" { + const entry = types.ManEntry{ + .name = "printf", + .section = "3", + .description = "print", + .path = null, + }; + const targets = &[_][]const u8{"1", "3", "8"}; + try std.testing.expect(entry.matchesSection(targets)); +} + +test "ManEntryList - basic operations" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var list = types.ManEntryList.init(allocator); + defer { + for (list.items) |entry| { + allocator.free(entry.name); + allocator.free(entry.section); + allocator.free(entry.description); + if (entry.path) |path| allocator.free(path); + } + list.deinit(); + } + + try list.append(types.ManEntry{ + .name = try allocator.dupe(u8, "ls"), + .section = try allocator.dupe(u8, "1"), + .description = try allocator.dupe(u8, "list"), + .path = null, + }); + + try std.testing.expectEqual(@as(usize, 1), list.items.len); + try std.testing.expectEqualStrings("ls", list.items[0].name); +} + +test "SearchConfig - struct creation" { + const config = types.SearchConfig{ + .keyword = "test", + .target_sections = null, + .show_paths = false, + .verbose = true, + }; + + try std.testing.expectEqualStrings("test", config.keyword); + try std.testing.expect(config.target_sections == null); + try std.testing.expect(!config.show_paths); + try std.testing.expect(config.verbose); +}