Table of Contents
Testing
Using a different testing renderer (in a corresponding namespace zterm.testing) renders without flushing to the screen, but keeps the corresponding zterm.Cell slice for comparison. The main question would rather be how to provide the expected value for the zterm.Cell slice.
The test renderer shall be configured with a given size (as the corresponding terminal for the screen size will not be available). This serves two aspects: Firstly, .resize Event triggering and secondly, the rendering into a zterm.Cell slice with the provided size dimensions (for testing layout handling and rendering (i.e. of a scrollable element, etc.)).
For testing Containers and/or Elements content generation the correspondingly rendered Cell slices are tested. For this the test creation is two stepped. The first step is the creation of the .zon file which contains the expected Cell slice for the screen you want to test (you can also test multiple screens - which would be necessary when testing for interactivity). Secondly you create the test case now against the created .zon file.
Zon Test file creation
test "create container zon file" {
const event = @import("event.zig");
const testing = @import("testing.zig");
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.border = .{
.color = .green,
.sides = .horizontal,
},
}, .{});
defer container.deinit();
const size: Point = .{
.x = 30,
.y = 20,
};
const allocator = std.testing.allocator;
var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit();
try renderer.resize(size);
container.resize(size);
container.reposition(.{});
try renderer.render(Container(event.SystemEvent), &container);
// NOTE: this is dependent on the working directory the test will be executed in
const file = try std.fs.cwd().createFile("src/test/<path>.zon", .{ .truncate = true });
defer file.close();
// write the rendered `Cell` slice to disk
try renderer.save(file.writer());
}
After running the test as usual you created the <path>.zon file accordingly in the specified directory. Afterwards you can create the test against that file and the test above should be replaced with the test below.
Test against zon file
test "test container against zon file" {
const event = @import("event.zig");
const testing = @import("testing.zig");
var container: Container(event.SystemEvent) = try .init(std.testing.allocator, .{
.border = .{
.color = .green,
.sides = .horizontal,
},
}, .{});
defer container.deinit();
try testing.expectContainerScreen(.{
.x = 30,
.y = 20,
}, &container, @import("test/<path>.zon"));
}
Testing user interactivity
When testing with further user interaction the zterm.testing.expectContainerScreen function cannot be used. The function creates a renderer, initializes the provided container and renders the screen for comparison.
This is the implementation of zterm.testing.expectContainerScreen:
pub fn expectContainerScreen(size: Size, container: *Container(event.SystemEvent), expected: []const Cell) !void {
const allocator = std.testing.allocator;
var renderer: Renderer = .init(allocator, size);
defer renderer.deinit();
try renderer.resize(size);
container.resize(size);
container.reposition(.{});
try renderer.render(Container(event.SystemEvent), container);
try expectEqualCells(.{}, renderer.size, expected, renderer.screen);
}
Tip
Create several checkpoint screens that should be tested against when testing for user interaction.
For testing user interaction you need to provide the container with the corresponding events it should receive. This includes user key presses, mouse presses (with their corresponding location on the screen), etc. For example see the test for Scrollable elements, which create the scrollable Container renders it and tests the screen, then scrolls down, re-renders the Container to test and finally tries to scroll further down to test for scrolling down after reaching the end of the Scrollable element:
test "scrollable vertical" {
const event = @import("event.zig");
const testing = @import("testing.zig");
const allocator = std.testing.allocator;
const size: Point = .{
.x = 30,
.y = 20,
};
var box: Container(event.SystemEvent) = try .init(allocator, .{
.border = .{
.sides = .all,
.color = .red,
},
.layout = .{
.separator = .{
.enabled = true,
.color = .red,
},
.direction = .vertical,
.padding = .all(1),
},
.size = .{
.dim = .{ .y = size.y + 15 },
},
}, .{});
try box.append(try .init(allocator, .{
.rectangle = .{ .fill = .grey },
}, .{}));
try box.append(try .init(allocator, .{
.rectangle = .{ .fill = .grey },
}, .{}));
defer box.deinit();
var scrollable: Scrollable(event.SystemEvent) = .init(box, .disabled);
var container: Container(event.SystemEvent) = try .init(allocator, .{
.border = .{
.color = .green,
.sides = .vertical,
},
}, scrollable.element());
defer container.deinit();
var renderer: testing.Renderer = .init(allocator, size);
defer renderer.deinit();
container.resize(size);
container.reposition(.{});
try renderer.render(Container(event.SystemEvent), &container);
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.top.zon"), renderer.screen);
// scroll down 15 times (exactly to the end)
for (0..15) |_| try container.handle(.{
.mouse = .{
.button = .wheel_down,
.kind = .press,
.x = 5,
.y = 5,
},
});
try renderer.render(Container(event.SystemEvent), &container);
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
// further scrolling down will not change anything
try container.handle(.{
.mouse = .{
.button = .wheel_down,
.kind = .press,
.x = 5,
.y = 5,
},
});
try renderer.render(Container(event.SystemEvent), &container);
try testing.expectEqualCells(.{}, renderer.size, @import("test/element/scrollable.vertical.bottom.zon"), renderer.screen);
}