Backlog/lib/zargs/tests/test_help.zig

294 lines
11 KiB
Zig

const std = @import("std");
const help = @import("help");
const ArgumentRegistry = @import("ArgumentRegistry").ArgumentRegistry;
const metadata = @import("metadata");
const ArgumentType = @import("ArgumentType").ArgumentType;
const SimpleConfig = struct {
verbose: bool = false,
output: []const u8 = "output.txt",
count: u32 = 10,
pub const meta = .{
.verbose = .{ .short = 'v', .help = "Enable verbose output" },
.output = .{ .short = 'o', .help = "Output file path" },
.count = .{ .short = 'c', .help = "Number of items to process" },
};
};
test "generate help text basic" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should contain usage line
try std.testing.expect(std.mem.indexOf(u8, help_text, "Usage:") != null);
// Should contain Options header
try std.testing.expect(std.mem.indexOf(u8, help_text, "Options:") != null);
// Should contain help flag
try std.testing.expect(std.mem.indexOf(u8, help_text, "--help") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "-h") != null);
}
test "generate help text with all arguments" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should contain all argument names
try std.testing.expect(std.mem.indexOf(u8, help_text, "--verbose") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "--output") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "--count") != null);
// Should contain short flags
try std.testing.expect(std.mem.indexOf(u8, help_text, "-v") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "-o") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "-c") != null);
}
test "generate help text with help descriptions" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should contain help text for each argument
try std.testing.expect(std.mem.indexOf(u8, help_text, "Enable verbose output") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "Output file path") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "Number of items to process") != null);
}
test "generate help text with default values" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should show default values
try std.testing.expect(std.mem.indexOf(u8, help_text, "[default: false]") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "[default: output.txt]") != null);
// Note: integer defaults are disabled, so count won't show default
}
test "generate help text with value placeholders" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Boolean should not have placeholder
const verbose_line_start = std.mem.indexOf(u8, help_text, "-v, --verbose").?;
const verbose_line_end = std.mem.indexOfPos(u8, help_text, verbose_line_start, "\n").?;
const verbose_line = help_text[verbose_line_start..verbose_line_end];
try std.testing.expect(std.mem.indexOf(u8, verbose_line, "<") == null);
// String should have <VALUE> placeholder
try std.testing.expect(std.mem.indexOf(u8, help_text, "--output <VALUE>") != null);
// Number should have <NUM> placeholder
try std.testing.expect(std.mem.indexOf(u8, help_text, "--count <NUM>") != null);
}
test "generate help text with program name" {
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(SimpleConfig, "test");
const help_text = try help.generateHelpText(&registry, std.testing.allocator, "myprogram");
defer std.testing.allocator.free(help_text);
// Should contain program name in usage line
try std.testing.expect(std.mem.indexOf(u8, help_text, "Usage: myprogram") != null);
}
test "generate help text with enum choices" {
const Mode = enum { fast, slow, balanced };
const EnumConfig = struct {
mode: Mode = .balanced,
pub const meta = .{
.mode = .{ .short = 'm', .help = "Processing mode" },
};
};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(EnumConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should contain enum choices (if implemented)
// Note: enum values extraction is currently disabled due to comptime limitations
// This test documents the expected behavior
}
test "generate help text alphabetical order" {
const UnorderedConfig = struct {
zebra: bool = false,
apple: bool = false,
middle: bool = false,
pub const meta = .{
.zebra = .{ .help = "Last" },
.apple = .{ .help = "First" },
.middle = .{ .help = "Middle" },
};
};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(UnorderedConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Find positions of each argument
const apple_pos = std.mem.indexOf(u8, help_text, "--apple").?;
const middle_pos = std.mem.indexOf(u8, help_text, "--middle").?;
const zebra_pos = std.mem.indexOf(u8, help_text, "--zebra").?;
// Should be in alphabetical order
try std.testing.expect(apple_pos < middle_pos);
try std.testing.expect(middle_pos < zebra_pos);
}
test "generate help text with optional fields" {
const OptionalConfig = struct {
name: ?[]const u8 = null,
age: ?u32 = null,
pub const meta = .{
.name = .{ .help = "Optional name" },
.age = .{ .help = "Optional age" },
};
};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(OptionalConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should contain optional fields
try std.testing.expect(std.mem.indexOf(u8, help_text, "--name") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "--age") != null);
// Optional fields should not be marked as required
try std.testing.expect(std.mem.indexOf(u8, help_text, "(required)") == null);
}
test "generate help text with string list" {
const ListConfig = struct {
files: []const []const u8 = &[_][]const u8{},
pub const meta = .{
.files = .{ .short = 'f', .help = "Input files" },
};
};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(ListConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should have <LIST> placeholder for string list
try std.testing.expect(std.mem.indexOf(u8, help_text, "--files <LIST>") != null);
}
test "generate help text alignment" {
const VaryingLengthConfig = struct {
a: bool = false,
very_long_argument_name: bool = false,
mid: bool = false,
pub const meta = .{
.a = .{ .help = "Short name" },
.very_long_argument_name = .{ .help = "Long name" },
.mid = .{ .help = "Medium name" },
};
};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(VaryingLengthConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Parse lines and check that help text starts at a consistent column
var lines = std.mem.splitSequence(u8, help_text, "\n");
var help_text_columns = std.ArrayListUnmanaged(usize){};
defer help_text_columns.deinit(std.testing.allocator);
while (lines.next()) |line| {
// Skip header lines
if (std.mem.indexOf(u8, line, "--") == null) continue;
// Find where the help text starts (after the argument name)
if (std.mem.indexOf(u8, line, "Short name")) |pos| {
try help_text_columns.append(std.testing.allocator, pos);
} else if (std.mem.indexOf(u8, line, "Long name")) |pos| {
try help_text_columns.append(std.testing.allocator, pos);
} else if (std.mem.indexOf(u8, line, "Medium name")) |pos| {
try help_text_columns.append(std.testing.allocator, pos);
}
}
// All help text should start at the same column (within reason)
if (help_text_columns.items.len >= 2) {
const first_col = help_text_columns.items[0];
for (help_text_columns.items[1..]) |col| {
// Allow some variation due to spacing, but should be close
const diff = if (col > first_col) col - first_col else first_col - col;
try std.testing.expect(diff < 5);
}
}
}
test "generate help with no arguments" {
const EmptyConfig = struct {};
var registry = ArgumentRegistry.init(std.testing.allocator);
defer registry.deinit();
try registry.registerMetadata(EmptyConfig, "test");
const help_text = try help.generateSimpleHelp(&registry, std.testing.allocator);
defer std.testing.allocator.free(help_text);
// Should still have basic structure
try std.testing.expect(std.mem.indexOf(u8, help_text, "Usage:") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "Options:") != null);
try std.testing.expect(std.mem.indexOf(u8, help_text, "--help") != null);
}