ai: add tests to tui

This commit is contained in:
Emil Lerch 2026-03-01 11:01:50 -08:00
parent 313ef83065
commit ce0238246f
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 190 additions and 0 deletions

View file

@ -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());
}

View file

@ -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());
}