From a1aad9e3e2b0a3b7230beae68d66f1c05bdc8bd7 Mon Sep 17 00:00:00 2001 From: phall Date: Sat, 15 Aug 2026 19:49:52 -0400 Subject: [PATCH] fix(automation): pair widget-key with key-up --- src/runtime/automation_widget_dispatch.zig | 17 ++++++- src/runtime/canvas_widget_event_tests.zig | 55 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/runtime/automation_widget_dispatch.zig b/src/runtime/automation_widget_dispatch.zig index 9209cdf0d..5aec44906 100644 --- a/src/runtime/automation_widget_dispatch.zig +++ b/src/runtime/automation_widget_dispatch.zig @@ -329,11 +329,12 @@ pub fn RuntimeAutomationWidgetDispatch(comptime Runtime: type) type { pub fn dispatchAutomationWidgetKeyInput(self: *Runtime, app: runtime_api.App(Runtime), key: AutomationWidgetKey) anyerror!void { const view_index = try automationGpuSurfaceViewIndexByLabel(self, key.view_label); try self.focusView(self.views[view_index].window_id, self.views[view_index].label); + const timestamp_ns = automationInputTimestampNs(); try self.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ .window_id = self.views[view_index].window_id, .label = self.views[view_index].label, .kind = .key_down, - .timestamp_ns = automationInputTimestampNs(), + .timestamp_ns = timestamp_ns, .key = key.key, .text = key.text, .modifiers = .{ @@ -344,6 +345,20 @@ pub fn RuntimeAutomationWidgetDispatch(comptime Runtime: type) type { .primary = key.modifiers.primary, }, } }); + try self.dispatchPlatformEvent(app, .{ .gpu_surface_input = .{ + .window_id = self.views[view_index].window_id, + .label = self.views[view_index].label, + .kind = .key_up, + .timestamp_ns = timestamp_ns, + .key = key.key, + .modifiers = .{ + .shift = key.modifiers.shift, + .control = key.modifiers.control, + .option = key.modifiers.option, + .command = key.modifiers.command, + .primary = key.modifiers.primary, + }, + } }); } /// Drive a trackpad pinch through the real platform-event path: diff --git a/src/runtime/canvas_widget_event_tests.zig b/src/runtime/canvas_widget_event_tests.zig index 99123ead6..4b2803263 100644 --- a/src/runtime/canvas_widget_event_tests.zig +++ b/src/runtime/canvas_widget_event_tests.zig @@ -3297,6 +3297,61 @@ test "runtime resizes retained canvas resizable widgets from pointer drag" { } } +test "automation widget-key dispatches a complete key lifetime" { + const TestApp = struct { + key_down_count: u32 = 0, + key_up_count: u32 = 0, + last_key: []const u8 = "", + key_down_timestamp_ns: u64 = 0, + last_timestamp_ns: u64 = 0, + + fn app(self: *@This()) App { + return .{ .context = self, .name = "automation-widget-key", .source = platform.WebViewSource.html("

GPU

"), .event_fn = event }; + } + + fn event(context: *anyopaque, runtime: *Runtime, event_value: Event) anyerror!void { + _ = runtime; + const self: *@This() = @ptrCast(@alignCast(context)); + switch (event_value) { + .gpu_surface_input => |input| switch (input.kind) { + .key_down => { + self.key_down_count += 1; + self.key_down_timestamp_ns = input.timestamp_ns; + }, + .key_up => { + self.key_up_count += 1; + self.last_key = input.key; + self.last_timestamp_ns = input.timestamp_ns; + }, + else => {}, + }, + else => {}, + } + } + }; + + const harness = try TestHarness().create(std.testing.allocator, .{}); + defer harness.destroy(std.testing.allocator); + harness.null_platform.gpu_surfaces = true; + var app_state: TestApp = .{}; + const app = app_state.app(); + try harness.start(app); + _ = try harness.runtime.createView(.{ + .window_id = 1, + .label = "canvas", + .kind = .gpu_surface, + .frame = geometry.RectF.init(0, 0, 320, 180), + }); + + try harness.runtime.dispatchAutomationCommand(app, "widget-key canvas cmd+g"); + + try std.testing.expectEqual(@as(u32, 1), app_state.key_down_count); + try std.testing.expectEqual(@as(u32, 1), app_state.key_up_count); + try std.testing.expectEqualStrings("g", app_state.last_key); + try std.testing.expect(app_state.last_timestamp_ns > 0); + try std.testing.expectEqual(app_state.key_down_timestamp_ns, app_state.last_timestamp_ns); +} + test "runtime dispatches automation canvas widget actions" { const TestApp = struct { command_count: u32 = 0,