keybind: add comprehensive tests for else= fallback

Add tests for:
- Multiple chains on the else branch
- Multiple chains on the performed branch before else
- Else with text/parameterized actions (allocated data)
- Clone with leaf_chained and else_actions
- Clone with text else_actions has independent memory
- Overwriting a binding with else_actions cleans up
- New binding after else resets chain context
- Else after unbind is error
- GenericLeaf exposes else_actions for both leaf and leaf_chained
- Performable without else has empty else_actions
- Non-performable binding has empty else_actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pull/11670/head
Steven Lu (MBP M1 Max) 2026-03-16 18:53:34 -04:00
parent bfe69b1c94
commit 3c90eea992
1 changed files with 286 additions and 0 deletions

View File

@ -5238,3 +5238,289 @@ test "set: formatEntries with chain and else" {
;
try testing.expectEqualStrings(expected, output.written());
}
test "set: parseAndPut multiple chains on else branch" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "else=new_tab");
try s.parseAndPut(alloc, "chain=close_surface");
try s.parseAndPut(alloc, "chain=toggle_fullscreen");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
const leaf = entry.leaf;
// Primary branch: just new_window (no chains before else)
try testing.expect(leaf.action == .new_window);
// Else branch: new_tab, close_surface, toggle_fullscreen
try testing.expectEqual(@as(usize, 3), leaf.else_actions.items.len);
try testing.expect(leaf.else_actions.items[0] == .new_tab);
try testing.expect(leaf.else_actions.items[1] == .close_surface);
try testing.expect(leaf.else_actions.items[2] == .toggle_fullscreen);
}
}
test "set: parseAndPut multiple chains on performed branch before else" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "chain=close_surface");
try s.parseAndPut(alloc, "chain=toggle_fullscreen");
try s.parseAndPut(alloc, "else=new_tab");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf_chained);
const chained = entry.leaf_chained;
// Performed branch: new_window, close_surface, toggle_fullscreen
try testing.expectEqual(@as(usize, 3), chained.actions.items.len);
try testing.expect(chained.actions.items[0] == .new_window);
try testing.expect(chained.actions.items[1] == .close_surface);
try testing.expect(chained.actions.items[2] == .toggle_fullscreen);
// Else branch: just new_tab
try testing.expectEqual(@as(usize, 1), chained.else_actions.items.len);
try testing.expect(chained.else_actions.items[0] == .new_tab);
}
}
test "set: parseAndPut else with text actions" {
const testing = std.testing;
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var s: Set = .{};
try s.parseAndPut(alloc, "performable:a=text:hello");
try s.parseAndPut(alloc, "else=text:world");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
try testing.expectEqualStrings("hello", entry.leaf.action.text);
try testing.expectEqual(@as(usize, 1), entry.leaf.else_actions.items.len);
try testing.expectEqualStrings("world", entry.leaf.else_actions.items[0].text);
}
}
test "set: clone with leaf_chained and else_actions" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "chain=close_surface");
try s.parseAndPut(alloc, "else=new_tab");
try s.parseAndPut(alloc, "chain=toggle_fullscreen");
var cloned = try s.clone(alloc);
defer cloned.deinit(alloc);
const entry = cloned.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf_chained);
// Performed branch preserved
try testing.expectEqual(@as(usize, 2), entry.leaf_chained.actions.items.len);
try testing.expect(entry.leaf_chained.actions.items[0] == .new_window);
try testing.expect(entry.leaf_chained.actions.items[1] == .close_surface);
// Else branch preserved
try testing.expectEqual(@as(usize, 2), entry.leaf_chained.else_actions.items.len);
try testing.expect(entry.leaf_chained.else_actions.items[0] == .new_tab);
try testing.expect(entry.leaf_chained.else_actions.items[1] == .toggle_fullscreen);
}
test "set: clone with text else_actions has independent memory" {
const testing = std.testing;
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var s: Set = .{};
try s.parseAndPut(alloc, "performable:a=text:hello");
try s.parseAndPut(alloc, "else=text:world");
const cloned = try s.clone(alloc);
const orig_entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
const cloned_entry = cloned.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
// Verify the cloned else action has the same content
try testing.expectEqualStrings("world", cloned_entry.leaf.else_actions.items[0].text);
// Verify the pointers are different (independent allocation)
try testing.expect(orig_entry.leaf.else_actions.items[0].text.ptr !=
cloned_entry.leaf.else_actions.items[0].text.ptr);
}
test "set: overwrite binding with else_actions cleans up" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
// Create a binding with else_actions
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "else=new_tab");
// Overwrite with a new binding old else_actions must be freed
try s.parseAndPut(alloc, "a=close_surface");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
try testing.expect(entry.leaf.action == .close_surface);
// New binding should have no else_actions
try testing.expectEqual(@as(usize, 0), entry.leaf.else_actions.items.len);
}
}
test "set: new binding after else resets chain context" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
// First binding with else
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "else=new_tab");
// New binding should start fresh, not in else mode
try s.parseAndPut(alloc, "b=close_surface");
try s.parseAndPut(alloc, "chain=toggle_fullscreen");
{
// Verify first binding unchanged
const a_entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(a_entry == .leaf);
try testing.expectEqual(@as(usize, 1), a_entry.leaf.else_actions.items.len);
// Verify second binding has chain on primary (not else)
const b_entry = s.get(.{ .key = .{ .unicode = 'b' } }).?.value_ptr.*;
try testing.expect(b_entry == .leaf_chained);
try testing.expectEqual(@as(usize, 2), b_entry.leaf_chained.actions.items.len);
try testing.expect(b_entry.leaf_chained.actions.items[0] == .close_surface);
try testing.expect(b_entry.leaf_chained.actions.items[1] == .toggle_fullscreen);
// No else on second binding
try testing.expectEqual(@as(usize, 0), b_entry.leaf_chained.else_actions.items.len);
}
}
test "set: else after unbind is error" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "a=unbind");
// chain_parent cleared by unbind, so else should fail
try testing.expectError(error.InvalidFormat, s.parseAndPut(alloc, "else=new_tab"));
}
test "set: generic leaf exposes else_actions" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "else=new_tab");
try s.parseAndPut(alloc, "chain=close_surface");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
const generic = entry.leaf.generic();
// Primary actions
const actions = generic.actionsSlice();
try testing.expectEqual(@as(usize, 1), actions.len);
try testing.expect(actions[0] == .new_window);
// Else actions accessible via generic
try testing.expectEqual(@as(usize, 2), generic.else_actions.len);
try testing.expect(generic.else_actions[0] == .new_tab);
try testing.expect(generic.else_actions[1] == .close_surface);
}
}
test "set: generic leaf_chained exposes else_actions" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
try s.parseAndPut(alloc, "chain=close_surface");
try s.parseAndPut(alloc, "else=new_tab");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf_chained);
const generic = entry.leaf_chained.generic();
// Primary actions
const actions = generic.actionsSlice();
try testing.expectEqual(@as(usize, 2), actions.len);
// Else actions accessible via generic
try testing.expectEqual(@as(usize, 1), generic.else_actions.len);
try testing.expect(generic.else_actions[0] == .new_tab);
}
}
test "set: performable without else has empty else_actions" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "performable:a=new_window");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
const generic = entry.leaf.generic();
try testing.expectEqual(@as(usize, 0), generic.else_actions.len);
}
}
test "set: non-performable binding has empty else_actions" {
const testing = std.testing;
const alloc = testing.allocator;
var s: Set = .{};
defer s.deinit(alloc);
try s.parseAndPut(alloc, "a=new_window");
{
const entry = s.get(.{ .key = .{ .unicode = 'a' } }).?.value_ptr.*;
try testing.expect(entry == .leaf);
try testing.expectEqual(@as(usize, 0), entry.leaf.else_actions.items.len);
}
}