@zogien/native-sdk 0.11.0-fork.3 → 0.11.0-fork.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/app.zig +53 -34
- package/build/cli.zig +22 -0
- package/package.json +10 -10
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/schemas/app.schema.json +9 -1
- package/skill-data/core/references/bridge-security-native-capabilities.md +4 -0
- package/skill-data/ts-core/SKILL.md +2 -0
- package/src/app_runner/app_assets.zig +85 -1
- package/src/app_runner/root.zig +63 -3
- package/src/app_runner/ts_core_main.zig +8 -0
- package/src/platform/linux/cef_host.cpp +22 -0
- package/src/platform/linux/gtk_host.c +317 -34
- package/src/platform/linux/gtk_host.h +37 -0
- package/src/platform/linux/root.zig +114 -20
- package/src/platform/macos/appkit_host.h +53 -0
- package/src/platform/macos/appkit_host.m +667 -67
- package/src/platform/macos/cef_host.mm +21 -0
- package/src/platform/macos/root.zig +146 -29
- package/src/platform/null_platform.zig +136 -0
- package/src/platform/root.zig +7 -0
- package/src/platform/types.zig +90 -0
- package/src/platform/windows/dialog_test.cpp +303 -0
- package/src/platform/windows/host_loop_test.cpp +169 -0
- package/src/platform/windows/root.zig +262 -39
- package/src/platform/windows/session_end_test.cpp +303 -0
- package/src/platform/windows/webview2_host.cpp +1435 -306
- package/src/primitives/app_manifest/root.zig +1 -0
- package/src/primitives/app_manifest/tests.zig +35 -0
- package/src/primitives/app_manifest/types.zig +12 -0
- package/src/primitives/app_manifest/validation.zig +44 -6
- package/src/root.zig +8 -0
- package/src/runtime/builtin_bridge.zig +108 -13
- package/src/runtime/channel_sources.zig +49 -1
- package/src/runtime/core.zig +24 -3
- package/src/runtime/dialog_tests.zig +367 -0
- package/src/runtime/dialogs.zig +385 -0
- package/src/runtime/effects.zig +83 -3
- package/src/runtime/effects_host_tests.zig +69 -1
- package/src/runtime/flow.zig +20 -6
- package/src/runtime/flow_reference.zig +20 -6
- package/src/runtime/platform_bridge_tests.zig +1 -0
- package/src/runtime/root.zig +9 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/runtime/system_services.zig +64 -82
- package/src/runtime/ui_app.zig +67 -0
- package/src/tooling/manifest.zig +111 -3
- package/src/tooling/package.zig +98 -8
- package/src/tooling/raw_manifest.zig +7 -0
- package/src/tooling/root.zig +2 -0
- package/src/tooling/templates.zig +44 -2
- package/src/tooling/update.zig +228 -10
- package/src/tooling/windows_installer.zig +245 -0
- package/src/tooling/zip_writer.zig +211 -0
- package/src/updater/engine.zig +936 -0
- package/src/updater/feed.zig +261 -12
- package/src/updater/macos.zig +187 -0
- package/src/updater/root.zig +41 -0
- package/src/updater/windows.zig +470 -0
- package/src/windows_install/config.zig +247 -0
- package/src/windows_install/main.zig +690 -0
- package/src/windows_install/payload.zig +146 -0
- package/src/windows_install/switch.zig +184 -0
- package/src/windows_install/tests.zig +11 -0
- package/src/windows_install/win32.zig +248 -0
- package/tools/native-sdk/main.zig +10 -9
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
const support = @import("test_support.zig");
|
|
2
|
+
const std = support.std;
|
|
3
|
+
const geometry = support.geometry;
|
|
4
|
+
const platform = support.platform;
|
|
5
|
+
const App = support.App;
|
|
6
|
+
const Runtime = support.Runtime;
|
|
7
|
+
const TestHarness = support.TestHarness;
|
|
8
|
+
const DialogCompletion = support.runtime_module.DialogCompletion;
|
|
9
|
+
|
|
10
|
+
const Probe = struct {
|
|
11
|
+
calls: usize = 0,
|
|
12
|
+
ids: [8]platform.DialogRequestId = undefined,
|
|
13
|
+
outcomes: [8]platform.DialogOutcome = undefined,
|
|
14
|
+
last_paths: [256]u8 = undefined,
|
|
15
|
+
last_paths_len: usize = 0,
|
|
16
|
+
last_count: usize = 0,
|
|
17
|
+
last_button: platform.MessageDialogResult = .primary,
|
|
18
|
+
last_tag: [64]u8 = undefined,
|
|
19
|
+
last_tag_len: usize = 0,
|
|
20
|
+
|
|
21
|
+
fn completion(self: *Probe, tag: []const u8) DialogCompletion {
|
|
22
|
+
return .{ .context = self, .tag = tag, .on_result = onResult };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn onResult(context: *anyopaque, runtime: *Runtime, tag: []const u8, result: platform.DialogResult) anyerror!void {
|
|
26
|
+
_ = runtime;
|
|
27
|
+
const self: *Probe = @ptrCast(@alignCast(context));
|
|
28
|
+
self.ids[self.calls] = result.id;
|
|
29
|
+
self.outcomes[self.calls] = result.outcome;
|
|
30
|
+
self.calls += 1;
|
|
31
|
+
self.last_paths_len = result.paths.len;
|
|
32
|
+
@memcpy(self.last_paths[0..result.paths.len], result.paths);
|
|
33
|
+
self.last_count = result.count;
|
|
34
|
+
self.last_button = result.button;
|
|
35
|
+
self.last_tag_len = tag.len;
|
|
36
|
+
@memcpy(self.last_tag[0..tag.len], tag);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn paths(self: *const Probe) []const u8 {
|
|
40
|
+
return self.last_paths[0..self.last_paths_len];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn tagText(self: *const Probe) []const u8 {
|
|
44
|
+
return self.last_tag[0..self.last_tag_len];
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
fn dialogApp(harness: *TestHarness()) App {
|
|
49
|
+
return .{ .context = harness, .name = "dialogs", .source = platform.WebViewSource.html("<p>Dialogs</p>") };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// A started runtime whose null platform presents dialogs asynchronously.
|
|
53
|
+
fn started() !*TestHarness() {
|
|
54
|
+
const harness = try startedBlocking();
|
|
55
|
+
harness.null_platform.useAsyncDialogs(&harness.runtime.options.platform.services);
|
|
56
|
+
return harness;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// The null platform as app tests get it: blocking dialogs that answer at
|
|
60
|
+
/// once.
|
|
61
|
+
fn startedBlocking() !*TestHarness() {
|
|
62
|
+
const harness = try TestHarness().create(std.testing.allocator, .{});
|
|
63
|
+
errdefer harness.destroy(std.testing.allocator);
|
|
64
|
+
try harness.runtime.dispatchPlatformEvent(dialogApp(harness), .app_start);
|
|
65
|
+
return harness;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Deliver the wakes the platform posted, as a host loop would.
|
|
69
|
+
fn deliverWakes(harness: *TestHarness()) !void {
|
|
70
|
+
while (harness.null_platform.takeWake()) |event| try harness.runtime.dispatchPlatformEvent(dialogApp(harness), event);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn closeMainWindow(harness: *TestHarness()) !void {
|
|
74
|
+
try harness.runtime.dispatchPlatformEvent(dialogApp(harness), .{ .window_frame_changed = .{
|
|
75
|
+
.id = 1,
|
|
76
|
+
.label = "main",
|
|
77
|
+
.title = "",
|
|
78
|
+
.frame = geometry.RectF.init(0, 0, 720, 480),
|
|
79
|
+
.scale_factor = 1,
|
|
80
|
+
.open = false,
|
|
81
|
+
.focused = false,
|
|
82
|
+
} });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test "dialog requests return at once and complete on a later wake" {
|
|
86
|
+
const harness = try started();
|
|
87
|
+
defer harness.destroy(std.testing.allocator);
|
|
88
|
+
var probe: Probe = .{};
|
|
89
|
+
|
|
90
|
+
const id = try harness.runtime.requestOpenDialog(.{ .title = "Open", .allow_multiple = true }, null, probe.completion("request-1"));
|
|
91
|
+
try std.testing.expectEqual(@as(usize, 0), probe.calls);
|
|
92
|
+
const shown = harness.null_platform.findPresentedDialog(id).?;
|
|
93
|
+
try std.testing.expectEqual(platform.DialogKind.open, shown.kind);
|
|
94
|
+
try std.testing.expectEqual(@as(?platform.WindowId, 1), shown.parent_window_id);
|
|
95
|
+
try std.testing.expectEqualStrings("Open", shown.titleText());
|
|
96
|
+
|
|
97
|
+
try harness.null_platform.finishDialog(id, .selected, "/a.txt\n/b.txt", .primary);
|
|
98
|
+
try std.testing.expectEqual(@as(usize, 0), probe.calls);
|
|
99
|
+
try deliverWakes(harness);
|
|
100
|
+
|
|
101
|
+
try std.testing.expectEqual(@as(usize, 1), probe.calls);
|
|
102
|
+
try std.testing.expectEqual(id, probe.ids[0]);
|
|
103
|
+
try std.testing.expectEqual(platform.DialogOutcome.selected, probe.outcomes[0]);
|
|
104
|
+
try std.testing.expectEqualStrings("/a.txt\n/b.txt", probe.paths());
|
|
105
|
+
try std.testing.expectEqual(@as(usize, 2), probe.last_count);
|
|
106
|
+
try std.testing.expectEqualStrings("request-1", probe.tagText());
|
|
107
|
+
try std.testing.expectEqual(@as(usize, 0), harness.runtime.pendingDialogCount());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
test "dialogs for one window show one at a time in request order" {
|
|
111
|
+
const harness = try started();
|
|
112
|
+
defer harness.destroy(std.testing.allocator);
|
|
113
|
+
var probe: Probe = .{};
|
|
114
|
+
|
|
115
|
+
const first = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
116
|
+
const second = try harness.runtime.requestMessageDialog(.{ .message = "Next" }, 1, probe.completion(""));
|
|
117
|
+
const detached = try harness.runtime.requestDialog(.{ .options = .{ .save = .{ .default_name = "a.txt" } }, .detached = true }, probe.completion(""));
|
|
118
|
+
try std.testing.expectEqual(@as(usize, 2), harness.null_platform.presentedDialogCount());
|
|
119
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(second) == null);
|
|
120
|
+
try std.testing.expectEqual(@as(?platform.WindowId, null), harness.null_platform.findPresentedDialog(detached).?.parent_window_id);
|
|
121
|
+
|
|
122
|
+
try harness.null_platform.finishDialog(first, .cancelled, "", .primary);
|
|
123
|
+
try deliverWakes(harness);
|
|
124
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(second) != null);
|
|
125
|
+
|
|
126
|
+
try harness.null_platform.finishDialog(detached, .selected, "/a.txt", .primary);
|
|
127
|
+
try deliverWakes(harness);
|
|
128
|
+
try harness.null_platform.finishDialog(second, .selected, "", .secondary);
|
|
129
|
+
try deliverWakes(harness);
|
|
130
|
+
|
|
131
|
+
try std.testing.expectEqual(@as(usize, 3), probe.calls);
|
|
132
|
+
try std.testing.expectEqualSlices(platform.DialogRequestId, &.{ first, detached, second }, probe.ids[0..3]);
|
|
133
|
+
try std.testing.expectEqual(platform.MessageDialogResult.secondary, probe.last_button);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
test "cancelling a queued dialog never shows it and a presented one closes" {
|
|
137
|
+
const harness = try started();
|
|
138
|
+
defer harness.destroy(std.testing.allocator);
|
|
139
|
+
var probe: Probe = .{};
|
|
140
|
+
|
|
141
|
+
const first = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
142
|
+
const second = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
143
|
+
harness.runtime.cancelDialog(second);
|
|
144
|
+
try std.testing.expectEqual(@as(usize, 0), probe.calls);
|
|
145
|
+
try deliverWakes(harness);
|
|
146
|
+
try std.testing.expectEqual(@as(usize, 1), probe.calls);
|
|
147
|
+
try std.testing.expectEqual(second, probe.ids[0]);
|
|
148
|
+
try std.testing.expectEqual(platform.DialogOutcome.cancelled, probe.outcomes[0]);
|
|
149
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.dialog_request_count);
|
|
150
|
+
|
|
151
|
+
harness.runtime.cancelDialog(first);
|
|
152
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.dialog_cancel_count);
|
|
153
|
+
try deliverWakes(harness);
|
|
154
|
+
try std.testing.expectEqual(@as(usize, 2), probe.calls);
|
|
155
|
+
try std.testing.expectEqual(platform.DialogOutcome.cancelled, probe.outcomes[1]);
|
|
156
|
+
harness.runtime.cancelDialog(first);
|
|
157
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.dialog_cancel_count);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test "closing the parent window cancels its dialogs and leaves others" {
|
|
161
|
+
const harness = try started();
|
|
162
|
+
defer harness.destroy(std.testing.allocator);
|
|
163
|
+
var probe: Probe = .{};
|
|
164
|
+
|
|
165
|
+
const shown = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
166
|
+
const queued = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
167
|
+
const detached = try harness.runtime.requestDialog(.{ .options = .{ .open = .{} }, .detached = true }, probe.completion(""));
|
|
168
|
+
try closeMainWindow(harness);
|
|
169
|
+
try deliverWakes(harness);
|
|
170
|
+
|
|
171
|
+
// Platform-reported results drain before the runtime's own.
|
|
172
|
+
try std.testing.expectEqual(@as(usize, 2), probe.calls);
|
|
173
|
+
try std.testing.expectEqualSlices(platform.DialogRequestId, &.{ shown, queued }, probe.ids[0..2]);
|
|
174
|
+
try std.testing.expectEqualSlices(platform.DialogOutcome, &.{ .cancelled, .cancelled }, probe.outcomes[0..2]);
|
|
175
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(detached) != null);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
test "an app stop drops pending dialogs without running completions" {
|
|
179
|
+
const harness = try started();
|
|
180
|
+
defer harness.destroy(std.testing.allocator);
|
|
181
|
+
var probe: Probe = .{};
|
|
182
|
+
|
|
183
|
+
_ = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
184
|
+
_ = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
185
|
+
try harness.runtime.dispatchPlatformEvent(dialogApp(harness), .app_shutdown);
|
|
186
|
+
try std.testing.expectEqual(@as(usize, 0), harness.runtime.pendingDialogCount());
|
|
187
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.dialog_cancel_count);
|
|
188
|
+
try deliverWakes(harness);
|
|
189
|
+
try std.testing.expectEqual(@as(usize, 0), probe.calls);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
test "a dialog the platform cannot present reports failed" {
|
|
193
|
+
const harness = try started();
|
|
194
|
+
defer harness.destroy(std.testing.allocator);
|
|
195
|
+
var probe: Probe = .{};
|
|
196
|
+
|
|
197
|
+
harness.null_platform.fail_next_dialog_request = true;
|
|
198
|
+
const failed = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
199
|
+
const next = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
200
|
+
try deliverWakes(harness);
|
|
201
|
+
try std.testing.expectEqual(@as(usize, 1), probe.calls);
|
|
202
|
+
try std.testing.expectEqual(failed, probe.ids[0]);
|
|
203
|
+
try std.testing.expectEqual(platform.DialogOutcome.failed, probe.outcomes[0]);
|
|
204
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(next) != null);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
test "platforms without asynchronous dialogs fall back to the blocking call" {
|
|
208
|
+
const harness = try startedBlocking();
|
|
209
|
+
defer harness.destroy(std.testing.allocator);
|
|
210
|
+
var probe: Probe = .{};
|
|
211
|
+
|
|
212
|
+
_ = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
213
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.open_dialog_count);
|
|
214
|
+
try std.testing.expectEqual(@as(usize, 0), probe.calls);
|
|
215
|
+
try deliverWakes(harness);
|
|
216
|
+
try std.testing.expectEqual(@as(usize, 1), probe.calls);
|
|
217
|
+
try std.testing.expectEqualStrings("/tmp/native-sdk-open.txt", probe.paths());
|
|
218
|
+
|
|
219
|
+
harness.null_platform.message_dialog_result = .tertiary;
|
|
220
|
+
_ = try harness.runtime.requestMessageDialog(.{ .message = "Sure?" }, null, probe.completion(""));
|
|
221
|
+
try deliverWakes(harness);
|
|
222
|
+
try std.testing.expectEqual(platform.DialogOutcome.selected, probe.outcomes[1]);
|
|
223
|
+
try std.testing.expectEqual(platform.MessageDialogResult.tertiary, probe.last_button);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
test "a completion may request the next dialog without jumping the queue" {
|
|
227
|
+
const harness = try started();
|
|
228
|
+
defer harness.destroy(std.testing.allocator);
|
|
229
|
+
const Chain = struct {
|
|
230
|
+
harness: *TestHarness(),
|
|
231
|
+
probe: Probe = .{},
|
|
232
|
+
third: platform.DialogRequestId = 0,
|
|
233
|
+
|
|
234
|
+
fn onResult(context: *anyopaque, runtime: *Runtime, tag: []const u8, result: platform.DialogResult) anyerror!void {
|
|
235
|
+
const self: *@This() = @ptrCast(@alignCast(context));
|
|
236
|
+
try Probe.onResult(&self.probe, runtime, tag, result);
|
|
237
|
+
if (self.third == 0) self.third = try runtime.requestOpenDialog(.{}, 1, .{ .context = self, .on_result = onResult });
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var chain: Chain = .{ .harness = harness };
|
|
241
|
+
|
|
242
|
+
const first = try harness.runtime.requestOpenDialog(.{}, null, .{ .context = &chain, .on_result = Chain.onResult });
|
|
243
|
+
const second = try harness.runtime.requestOpenDialog(.{}, null, .{ .context = &chain, .on_result = Chain.onResult });
|
|
244
|
+
try harness.null_platform.finishDialog(first, .cancelled, "", .primary);
|
|
245
|
+
try deliverWakes(harness);
|
|
246
|
+
try std.testing.expect(chain.third != 0);
|
|
247
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(second) != null);
|
|
248
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(chain.third) == null);
|
|
249
|
+
|
|
250
|
+
try harness.null_platform.finishDialog(second, .cancelled, "", .primary);
|
|
251
|
+
try deliverWakes(harness);
|
|
252
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(chain.third) != null);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
test "dialog requests are validated before they queue" {
|
|
256
|
+
const harness = try started();
|
|
257
|
+
defer harness.destroy(std.testing.allocator);
|
|
258
|
+
var probe: Probe = .{};
|
|
259
|
+
|
|
260
|
+
const long_tag = [_]u8{'t'} ** (support.runtime_module.max_dialog_tag_bytes + 1);
|
|
261
|
+
try std.testing.expectError(error.DialogTagTooLarge, harness.runtime.requestOpenDialog(.{}, null, probe.completion(&long_tag)));
|
|
262
|
+
try std.testing.expectError(error.InvalidDialogOptions, harness.runtime.requestMessageDialog(.{ .primary_button = "" }, null, probe.completion("")));
|
|
263
|
+
const long_title = [_]u8{'x'} ** (platform.max_dialog_title_bytes + 1);
|
|
264
|
+
try std.testing.expectError(error.DialogFieldTooLarge, harness.runtime.requestOpenDialog(.{ .title = &long_title }, null, probe.completion("")));
|
|
265
|
+
try std.testing.expectError(error.WindowNotFound, harness.runtime.requestOpenDialog(.{}, 99, probe.completion("")));
|
|
266
|
+
try std.testing.expectEqual(@as(usize, 0), harness.runtime.pendingDialogCount());
|
|
267
|
+
|
|
268
|
+
for (0..support.runtime_module.max_dialog_requests) |_| _ = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
269
|
+
try std.testing.expectError(error.DialogQueueFull, harness.runtime.requestOpenDialog(.{}, null, probe.completion("")));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
test "a failing completion is recorded and the queue moves on" {
|
|
273
|
+
const harness = try started();
|
|
274
|
+
defer harness.destroy(std.testing.allocator);
|
|
275
|
+
const Failing = struct {
|
|
276
|
+
fn onResult(context: *anyopaque, runtime: *Runtime, tag: []const u8, result: platform.DialogResult) anyerror!void {
|
|
277
|
+
_ = .{ context, runtime, tag, result };
|
|
278
|
+
return error.CompletionFailed;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var probe: Probe = .{};
|
|
282
|
+
const first = try harness.runtime.requestOpenDialog(.{}, null, .{ .context = &probe, .on_result = Failing.onResult });
|
|
283
|
+
const second = try harness.runtime.requestOpenDialog(.{}, null, probe.completion(""));
|
|
284
|
+
const errors_before = harness.runtime.dispatch_error_total;
|
|
285
|
+
try harness.null_platform.finishDialog(first, .cancelled, "", .primary);
|
|
286
|
+
try deliverWakes(harness);
|
|
287
|
+
try std.testing.expectEqual(errors_before + 1, harness.runtime.dispatch_error_total);
|
|
288
|
+
try std.testing.expect(harness.null_platform.findPresentedDialog(second) != null);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fn allowDialogBridge(harness: *TestHarness()) void {
|
|
292
|
+
const permissions = struct {
|
|
293
|
+
const dialog = [_][]const u8{support.security.permission_dialog};
|
|
294
|
+
const commands = [_]support.bridge.CommandPolicy{
|
|
295
|
+
.{ .name = "native-sdk.dialog.openFile", .permissions = &dialog, .origins = &.{"zero://inline"} },
|
|
296
|
+
.{ .name = "native-sdk.dialog.saveFile", .permissions = &dialog, .origins = &.{"zero://inline"} },
|
|
297
|
+
.{ .name = "native-sdk.dialog.showMessage", .permissions = &dialog, .origins = &.{"zero://inline"} },
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
harness.runtime.options.security.permissions = &permissions.dialog;
|
|
301
|
+
harness.runtime.options.builtin_bridge = .{ .enabled = true, .commands = &permissions.commands };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn sendBridge(harness: *TestHarness(), bytes: []const u8) !void {
|
|
305
|
+
try harness.runtime.dispatchPlatformEvent(dialogApp(harness), .{ .bridge_message = .{ .bytes = bytes, .origin = "zero://inline", .window_id = 1 } });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
test "built-in bridge dialogs answer when the dialog finishes" {
|
|
309
|
+
const harness = try started();
|
|
310
|
+
defer harness.destroy(std.testing.allocator);
|
|
311
|
+
allowDialogBridge(harness);
|
|
312
|
+
|
|
313
|
+
try sendBridge(harness, "{\"id\":\"pick\",\"command\":\"native-sdk.dialog.openFile\",\"payload\":{\"title\":\"Pick\",\"allowMultiple\":true}}");
|
|
314
|
+
try std.testing.expectEqualStrings("", harness.null_platform.lastBridgeResponse());
|
|
315
|
+
const shown = harness.null_platform.findPresentedDialog(1).?;
|
|
316
|
+
try std.testing.expectEqualStrings("Pick", shown.titleText());
|
|
317
|
+
try std.testing.expectEqual(@as(?platform.WindowId, 1), shown.parent_window_id);
|
|
318
|
+
|
|
319
|
+
try harness.null_platform.finishDialog(1, .selected, "/a.txt\n/b.txt", .primary);
|
|
320
|
+
try deliverWakes(harness);
|
|
321
|
+
const response = harness.null_platform.lastBridgeResponse();
|
|
322
|
+
try std.testing.expect(std.mem.indexOf(u8, response, "\"id\":\"pick\"") != null);
|
|
323
|
+
try std.testing.expect(std.mem.indexOf(u8, response, "[\"/a.txt\",\"/b.txt\"]") != null);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
test "built-in bridge dialogs report dismissal, cancellation and failure" {
|
|
327
|
+
const harness = try started();
|
|
328
|
+
defer harness.destroy(std.testing.allocator);
|
|
329
|
+
allowDialogBridge(harness);
|
|
330
|
+
|
|
331
|
+
try sendBridge(harness, "{\"id\":\"ask\",\"command\":\"native-sdk.dialog.showMessage\",\"payload\":{\"message\":\"Delete?\",\"primaryButton\":\"Delete\",\"secondaryButton\":\"Keep\"}}");
|
|
332
|
+
try harness.null_platform.finishDialog(1, .cancelled, "", .primary);
|
|
333
|
+
try deliverWakes(harness);
|
|
334
|
+
try std.testing.expect(std.mem.indexOf(u8, harness.null_platform.lastBridgeResponse(), "\"dismissed\"") != null);
|
|
335
|
+
|
|
336
|
+
try sendBridge(harness, "{\"id\":\"save\",\"command\":\"native-sdk.dialog.saveFile\",\"payload\":{\"defaultName\":\"a.txt\"}}");
|
|
337
|
+
try harness.null_platform.finishDialog(2, .cancelled, "", .primary);
|
|
338
|
+
try deliverWakes(harness);
|
|
339
|
+
const saved = harness.null_platform.lastBridgeResponse();
|
|
340
|
+
try std.testing.expect(std.mem.indexOf(u8, saved, "\"id\":\"save\"") != null);
|
|
341
|
+
try std.testing.expect(std.mem.indexOf(u8, saved, "null") != null);
|
|
342
|
+
|
|
343
|
+
harness.null_platform.fail_next_dialog_request = true;
|
|
344
|
+
try sendBridge(harness, "{\"id\":\"broken\",\"command\":\"native-sdk.dialog.openFile\",\"payload\":{}}");
|
|
345
|
+
try deliverWakes(harness);
|
|
346
|
+
const broken = harness.null_platform.lastBridgeResponse();
|
|
347
|
+
try std.testing.expect(std.mem.indexOf(u8, broken, "\"id\":\"broken\"") != null);
|
|
348
|
+
try std.testing.expect(std.mem.indexOf(u8, broken, "\"internal_error\"") != null);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
test "built-in bridge dialogs on platforms without asynchronous dialogs answer at once" {
|
|
352
|
+
const harness = try startedBlocking();
|
|
353
|
+
defer harness.destroy(std.testing.allocator);
|
|
354
|
+
allowDialogBridge(harness);
|
|
355
|
+
|
|
356
|
+
try sendBridge(harness, "{\"id\":\"pick\",\"command\":\"native-sdk.dialog.openFile\",\"payload\":{\"title\":\"Pick\"}}");
|
|
357
|
+
const picked = harness.null_platform.lastBridgeResponse();
|
|
358
|
+
try std.testing.expect(std.mem.indexOf(u8, picked, "\"id\":\"pick\"") != null);
|
|
359
|
+
try std.testing.expect(std.mem.indexOf(u8, picked, "[\"/tmp/native-sdk-open.txt\"]") != null);
|
|
360
|
+
|
|
361
|
+
harness.null_platform.message_dialog_result = .secondary;
|
|
362
|
+
try sendBridge(harness, "{\"id\":\"ask\",\"command\":\"native-sdk.dialog.showMessage\",\"payload\":{\"message\":\"Delete?\",\"secondaryButton\":\"Keep\"}}");
|
|
363
|
+
try std.testing.expect(std.mem.indexOf(u8, harness.null_platform.lastBridgeResponse(), "\"secondary\"") != null);
|
|
364
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.open_dialog_count);
|
|
365
|
+
try std.testing.expectEqual(@as(usize, 1), harness.null_platform.message_dialog_count);
|
|
366
|
+
try std.testing.expectEqual(@as(usize, 0), harness.runtime.pendingDialogCount());
|
|
367
|
+
}
|