ai: add tests to tui
This commit is contained in:
parent
313ef83065
commit
ce0238246f
2 changed files with 190 additions and 0 deletions
113
src/tui.zig
113
src/tui.zig
|
|
@ -4032,3 +4032,116 @@ fn launchEditor(allocator: std.mem.Allocator, portfolio_path: ?[]const u8, watch
|
||||||
child.spawn() catch return;
|
child.spawn() catch return;
|
||||||
_ = child.wait() catch {};
|
_ = child.wait() catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Tests ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "colLabel plain left-aligned" {
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const result = colLabel(&buf, "Name", 10, true, null);
|
||||||
|
try testing.expectEqualStrings("Name ", result);
|
||||||
|
try testing.expectEqual(@as(usize, 10), result.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "colLabel plain right-aligned" {
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const result = colLabel(&buf, "Price", 10, false, null);
|
||||||
|
try testing.expectEqualStrings(" Price", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "colLabel with indicator left-aligned" {
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
const result = colLabel(&buf, "Name", 10, true, "\xe2\x96\xb2"); // ▲ = 3 bytes
|
||||||
|
// Indicator + text + padding. Display width is 10, byte length is 10 - 1 + 3 = 12
|
||||||
|
try testing.expectEqual(@as(usize, 12), result.len);
|
||||||
|
try testing.expect(std.mem.startsWith(u8, result, "\xe2\x96\xb2")); // starts with ▲
|
||||||
|
try testing.expect(std.mem.indexOf(u8, result, "Name") != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "colLabel with indicator right-aligned" {
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
const result = colLabel(&buf, "Price", 10, false, "\xe2\x96\xbc"); // ▼
|
||||||
|
try testing.expectEqual(@as(usize, 12), result.len);
|
||||||
|
try testing.expect(std.mem.endsWith(u8, result, "Price"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "glyph ASCII returns single-char slice" {
|
||||||
|
try testing.expectEqualStrings("A", glyph('A'));
|
||||||
|
try testing.expectEqualStrings(" ", glyph(' '));
|
||||||
|
try testing.expectEqualStrings("0", glyph('0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "glyph non-ASCII returns space" {
|
||||||
|
try testing.expectEqualStrings(" ", glyph(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PortfolioSortField next/prev" {
|
||||||
|
// next from first field
|
||||||
|
try testing.expectEqual(PortfolioSortField.shares, PortfolioSortField.symbol.next().?);
|
||||||
|
// next from last field returns null
|
||||||
|
try testing.expectEqual(@as(?PortfolioSortField, null), PortfolioSortField.account.next());
|
||||||
|
// prev from first returns null
|
||||||
|
try testing.expectEqual(@as(?PortfolioSortField, null), PortfolioSortField.symbol.prev());
|
||||||
|
// prev from last
|
||||||
|
try testing.expectEqual(PortfolioSortField.weight, PortfolioSortField.account.prev().?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "PortfolioSortField label" {
|
||||||
|
try testing.expectEqualStrings("Symbol", PortfolioSortField.symbol.label());
|
||||||
|
try testing.expectEqualStrings("Market Value", PortfolioSortField.market_value.label());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "SortDirection flip and indicator" {
|
||||||
|
try testing.expectEqual(SortDirection.desc, SortDirection.asc.flip());
|
||||||
|
try testing.expectEqual(SortDirection.asc, SortDirection.desc.flip());
|
||||||
|
try testing.expectEqualStrings("\xe2\x96\xb2", SortDirection.asc.indicator()); // ▲
|
||||||
|
try testing.expectEqualStrings("\xe2\x96\xbc", SortDirection.desc.indicator()); // ▼
|
||||||
|
}
|
||||||
|
|
||||||
|
test "buildBlockBar empty" {
|
||||||
|
const bar = try App.buildBlockBar(testing.allocator, 0, 10);
|
||||||
|
defer testing.allocator.free(bar);
|
||||||
|
// All spaces
|
||||||
|
try testing.expectEqual(@as(usize, 10), bar.len);
|
||||||
|
try testing.expectEqualStrings(" ", bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "buildBlockBar full" {
|
||||||
|
const bar = try App.buildBlockBar(testing.allocator, 1.0, 5);
|
||||||
|
defer testing.allocator.free(bar);
|
||||||
|
// 5 full blocks, each 3 bytes UTF-8 (█ = E2 96 88)
|
||||||
|
try testing.expectEqual(@as(usize, 15), bar.len);
|
||||||
|
// Verify first block is █
|
||||||
|
try testing.expectEqualStrings("\xe2\x96\x88", bar[0..3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "buildBlockBar partial" {
|
||||||
|
const bar = try App.buildBlockBar(testing.allocator, 0.5, 10);
|
||||||
|
defer testing.allocator.free(bar);
|
||||||
|
// 50% of 10 chars = 5 full blocks (no partial)
|
||||||
|
// 5 full blocks (15 bytes) + 5 spaces = 20 bytes
|
||||||
|
try testing.expectEqual(@as(usize, 20), bar.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "fmtBreakdownLine formats correctly" {
|
||||||
|
var arena_state = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena_state.deinit();
|
||||||
|
const arena = arena_state.allocator();
|
||||||
|
|
||||||
|
const item = zfin.analysis.BreakdownItem{
|
||||||
|
.label = "US Stock",
|
||||||
|
.weight = 0.65,
|
||||||
|
.value = 130000,
|
||||||
|
};
|
||||||
|
const line = try App.fmtBreakdownLine(arena, item, 10, 12);
|
||||||
|
// Should contain the label, percentage, and dollar amount
|
||||||
|
try testing.expect(std.mem.indexOf(u8, line, "US Stock") != null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, line, "65.0%") != null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, line, "$130,000") != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Tab label" {
|
||||||
|
try testing.expectEqualStrings(" 1:Portfolio ", Tab.portfolio.label());
|
||||||
|
try testing.expectEqualStrings(" 6:Analysis ", Tab.analysis.label());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -527,3 +527,80 @@ fn drawRect(ctx: *Context, alloc: std.mem.Allocator, x1: f64, y1: f64, x2: f64,
|
||||||
try ctx.stroke();
|
try ctx.stroke();
|
||||||
ctx.setLineWidth(2.0);
|
ctx.setLineWidth(2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Tests ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test "mapY maps value to pixel coordinate" {
|
||||||
|
// value at min → bottom
|
||||||
|
try std.testing.expectEqual(@as(f64, 500.0), mapY(0, 0, 100, 100, 500));
|
||||||
|
// value at max → top
|
||||||
|
try std.testing.expectEqual(@as(f64, 100.0), mapY(100, 0, 100, 100, 500));
|
||||||
|
// value at midpoint → midpoint
|
||||||
|
try std.testing.expectEqual(@as(f64, 300.0), mapY(50, 0, 100, 100, 500));
|
||||||
|
// flat range → midpoint
|
||||||
|
try std.testing.expectEqual(@as(f64, 300.0), mapY(42, 42, 42, 100, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "blendColor alpha blending" {
|
||||||
|
const white = [3]u8{ 255, 255, 255 };
|
||||||
|
const black = [3]u8{ 0, 0, 0 };
|
||||||
|
|
||||||
|
// Full alpha → foreground
|
||||||
|
const full = blendColor(white, 255, black);
|
||||||
|
try std.testing.expectEqual(@as(u8, 255), full.rgb.r);
|
||||||
|
try std.testing.expectEqual(@as(u8, 255), full.rgb.g);
|
||||||
|
|
||||||
|
// Zero alpha → background
|
||||||
|
const zero = blendColor(white, 0, black);
|
||||||
|
try std.testing.expectEqual(@as(u8, 0), zero.rgb.r);
|
||||||
|
|
||||||
|
// Half alpha → midpoint
|
||||||
|
const half = blendColor(white, 128, black);
|
||||||
|
// 255 * (128/255) + 0 * (127/255) ≈ 128
|
||||||
|
try std.testing.expect(half.rgb.r >= 127 and half.rgb.r <= 129);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "opaqueColor wraps theme color" {
|
||||||
|
const px = opaqueColor(.{ 0x7f, 0xd8, 0x8f });
|
||||||
|
try std.testing.expectEqual(@as(u8, 0x7f), px.rgb.r);
|
||||||
|
try std.testing.expectEqual(@as(u8, 0xd8), px.rgb.g);
|
||||||
|
try std.testing.expectEqual(@as(u8, 0x8f), px.rgb.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ChartConfig.parse" {
|
||||||
|
// Named modes
|
||||||
|
const auto = ChartConfig.parse("auto").?;
|
||||||
|
try std.testing.expectEqual(ChartMode.auto, auto.mode);
|
||||||
|
|
||||||
|
const braille = ChartConfig.parse("braille").?;
|
||||||
|
try std.testing.expectEqual(ChartMode.braille, braille.mode);
|
||||||
|
|
||||||
|
// WxH format
|
||||||
|
const custom = ChartConfig.parse("800x600").?;
|
||||||
|
try std.testing.expectEqual(ChartMode.kitty, custom.mode);
|
||||||
|
try std.testing.expectEqual(@as(u32, 800), custom.max_width);
|
||||||
|
try std.testing.expectEqual(@as(u32, 600), custom.max_height);
|
||||||
|
|
||||||
|
// Too small
|
||||||
|
try std.testing.expectEqual(@as(?ChartConfig, null), ChartConfig.parse("50x50"));
|
||||||
|
|
||||||
|
// Invalid
|
||||||
|
try std.testing.expectEqual(@as(?ChartConfig, null), ChartConfig.parse("garbage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Timeframe next/prev cycle" {
|
||||||
|
// next cycles through all values
|
||||||
|
try std.testing.expectEqual(Timeframe.ytd, Timeframe.@"6M".next());
|
||||||
|
try std.testing.expectEqual(Timeframe.@"1Y", Timeframe.ytd.next());
|
||||||
|
try std.testing.expectEqual(Timeframe.@"6M", Timeframe.@"5Y".next()); // wraps
|
||||||
|
|
||||||
|
// prev is the reverse
|
||||||
|
try std.testing.expectEqual(Timeframe.@"5Y", Timeframe.@"6M".prev()); // wraps
|
||||||
|
try std.testing.expectEqual(Timeframe.@"6M", Timeframe.ytd.prev());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Timeframe tradingDays" {
|
||||||
|
try std.testing.expectEqual(@as(usize, 126), Timeframe.@"6M".tradingDays());
|
||||||
|
try std.testing.expectEqual(@as(usize, 252), Timeframe.@"1Y".tradingDays());
|
||||||
|
try std.testing.expectEqual(@as(usize, 1260), Timeframe.@"5Y".tradingDays());
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue