ReviewOS

also looking at this

craft-native/craft

test(window): make "every window can be reopened" a property the build checks

#76
Merged glennmichael123 wants to merge feat/window-lifecycle-conformance into main
4 files +432 -23
packages/zig/src/window_registry.zigadded+160-0
Changes to packages/zig/src/window_registry.zig
@@ -0,0 +1,160 @@
1//! Which windows craft opened.
2//!
3//! The Dock's reopen event arrives with no argument: AppKit hands over the
4//! application and craft has to work out, from `[NSApp windows]`, which of them
5//! are its own. That list also holds the offscreen windows AppKit keeps for
6//! menus, tooltips and its own bookkeeping, and ordering one of those front
7//! puts an empty frame on screen.
8//!
9//! ## Why this is a list and not a test
10//!
11//! The first version asked the window: is your content view a `WKWebView`?
12//! That is true of craft's plain window and false of three others —
13//! `--web-sidebar-material` installs a backdrop container, and both sidebar
14//! constructors install a container or a split view controller. All three kept
15//! their closed window alive, so reopen found them, skipped them, and left the
16//! app activated with nothing on screen: exactly the dead end the reopen
17//! handler exists to remove.
18//!
19//! Nothing caught it. It compiles, every test passes, and it fails only against
20//! a real reopen event on a window style no test constructs.
21//!
22//! Recording the answer instead of inferring it fixes that, and it does
23//! something else worth more: it turns an untestable property into a
24//! checkable one. "Does this window look like ours?" can only be answered by
25//! having the window. "Did every constructor register?" is a property of the
26//! source, and `test/window_lifecycle_test.zig` checks it.
27
28const std = ;
29
30/// An opaque window handle — `@intFromPtr(NSWindow)` at the call site. Kept
31/// opaque so this module stays free of Objective-C and can be tested without
32/// AppKit.
33pub const Handle = usize;
34
35/// Craft opens one window today; #67 opens more. Far past any real app, and
36/// fixed so registration needs no allocator on the window-creation path.
37pub const capacity = 16;
38
39var windows: [capacity]Handle = @splat(0);
40
41/// Record a window craft created. Idempotent.
42///
43/// Returns false if the table was full and the window was not recorded — the
44/// caller is expected to say so rather than let a window silently become
45/// unreopenable.
46pub fn remember(handle: Handle) bool {
47 if (handle == 0) return false;
48 for (&windows) |*slot| {
49 if (slot.* == handle) return true;
50 if (slot.* == 0) {
51 slot.* = handle;
52 return true;
53 }
54 }
55 return false;
56}
57
58/// Whether craft opened this window.
59pub fn isKnown(handle: Handle) bool {
60 if (handle == 0) return false;
61 for (windows) |known| {
62 if (known == handle) return true;
63 }
64 return false;
65}
66
67/// Drop a window. For real teardown, whenever #67 introduces some.
68pub fn forget(handle: Handle) void {
69 for (&windows) |*slot| {
70 if (slot.* == handle) slot.* = 0;
71 }
72}
73
74pub fn count() usize {
75 var n: usize = 0;
76 for (windows) |w| {
77 if (w != 0) n += 1;
78 }
79 return n;
80}
81
82pub fn resetForTesting() void {
83 windows = @splat(0);
84}
85
86const testing = std.testing;
87
88test "a window craft opened is recognised" {
89 resetForTesting();
90 try testing.expect(remember(0x1000));
91 try testing.expect(isKnown(0x1000));
92}
93
94test "a window craft did not open is not" {
95 // The offscreen windows AppKit keeps for menus and tooltips land here.
96 // Ordering one of those front puts an empty frame on screen.
97 resetForTesting();
98 _ = remember(0x1000);
99 try testing.expect(!isKnown(0x2000));
100}
101
102test "registering twice does not consume two slots" {
103 resetForTesting();
104 try testing.expect(remember(0x1000));
105 try testing.expect(remember(0x1000));
106 try testing.expectEqual(@as(usize, 1), count());
107}
108
109test "a null window is neither recorded nor recognised" {
110 // `contentView` and friends return nil often enough that zero has to mean
111 // nothing rather than becoming a real entry that matches every other nil.
112 resetForTesting();
113 try testing.expect(!remember(0));
114 try testing.expect(!isKnown(0));
115 try testing.expectEqual(@as(usize, 0), count());
116}
117
118test "every window up to capacity is recorded" {
119 resetForTesting();
120 var i: usize = 1;
121 while (i <= capacity) : (i += 1) {
122 try testing.expect(remember(i * 0x100));
123 }
124 i = 1;
125 while (i <= capacity) : (i += 1) {
126 try testing.expect(isKnown(i * 0x100));
127 }
128 try testing.expectEqual(capacity, count());
129}
130
131test "past capacity the failure is reported, not swallowed" {
132 // The caller logs this. A window that quietly stopped being reopenable is
133 // the bug this whole module exists to prevent, so it must not be the
134 // silent outcome of a full table.
135 resetForTesting();
136 var i: usize = 1;
137 while (i <= capacity) : (i += 1) _ = remember(i * 0x100);
138 try testing.expect(!remember(0xDEAD));
139 try testing.expect(!isKnown(0xDEAD));
140}
141
142test "forgetting frees the slot for the next window" {
143 resetForTesting();
144 var i: usize = 1;
145 while (i <= capacity) : (i += 1) _ = remember(i * 0x100);
146 try testing.expect(!remember(0xDEAD));
147
148 forget(0x100);
149 try testing.expect(!isKnown(0x100));
150 try testing.expect(remember(0xDEAD));
151 try testing.expect(isKnown(0xDEAD));
152}
153
154test "forgetting a window that was never known changes nothing" {
155 resetForTesting();
156 _ = remember(0x1000);
157 forget(0x2000);
158 try testing.expect(isKnown(0x1000));
159 try testing.expectEqual(@as(usize, 1), count());
160}