1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! The keyboard driver the §11 focus tests steer by: one persistent
//! `egui::Context` across frames, real key events in, and egui's own
//! `wants_keyboard_input()` out. Split from [`super::focus`] for §12's budget —
//! the driver is how a frame is run, the tests are what a frame must show.
use super::super::render;
use super::fixture::World;
use crate::cli_outbound::Cli;
/// The window under test: one persistent `egui::Context`, so focus carries
/// frame to frame exactly as it does for the operator.
pub(super) struct Screen {
ctx: egui::Context,
lernie: Cli,
bl: Cli,
bz: Cli,
}
impl Screen {
/// Binaries that deliberately do not exist: the send test below is the one
/// acceptance case that actually *dispatches*, and what it asserts is where
/// the keyboard ends up, not what `lernie` did. A name nothing resolves
/// fails the spawn at once, so the frame never forks the operator's real
/// substrate to prove a focus rule.
pub(super) fn new() -> Self {
Self {
ctx: egui::Context::default(),
lernie: Cli::new("yog-absent-lernie"),
bl: Cli::new("yog-absent-bl"),
bz: Cli::new("yog-absent-bz"),
}
}
/// The same driver with a `lernie` that really answers — for the one
/// acceptance case whose gesture has to *succeed* end to end (the §3.4
/// raise, whose surface afterwards is what bl-9acf is about). `bl` and `bz`
/// stay absent: a bare rung mutates no ball and paints no login.
pub(super) fn with_lernie(lernie: Cli) -> Self {
Self {
lernie,
..Self::new()
}
}
/// Run one frame on `events` and report whether a text box holds the
/// keyboard afterwards.
pub(super) fn frame(&self, world: &mut World, events: Vec<egui::Event>) -> bool {
let _ = self.run(world, events);
self.ctx.wants_keyboard_input()
}
/// Run one idle frame and return every painted galley's text — the same
/// read [`super::painted`] makes, but on **this** screen's persistent
/// context, so what a box shows can be asserted after the keyboard has
/// typed into it.
pub(super) fn text(&self, world: &mut World) -> String {
let out = self.run(world, Vec::new());
crate::paint_probe::text_of(&out)
}
/// Run one frame and hand back everything it painted. A **pointer** test has
/// to find its coordinate before it can click one: a widget's rect is not
/// addressable from outside the frame that built it, but the galley it
/// painted is, and a key names the same seat far more stably than a number.
pub(super) fn shapes(
&self,
world: &mut World,
events: Vec<egui::Event>,
) -> Vec<egui::epaint::ClippedShape> {
self.output(world, events).shapes
}
/// One frame, whole — for a test that must read two things off the **same**
/// frame (what a run says and what hue it was painted in), which two calls
/// could not honestly give it.
pub(super) fn output(&self, world: &mut World, events: Vec<egui::Event>) -> egui::FullOutput {
self.run(world, events)
}
/// One frame of the real window.
fn run(&self, world: &mut World, events: Vec<egui::Event>) -> egui::FullOutput {
let input = egui::RawInput {
// The held modifiers ride `RawInput` beside the events, not only on
// them — `keys::handle` reads `i.modifiers`, so a combo spelled only
// on the event would arrive as its bare twin and the plane under
// test would be the wrong one.
modifiers: modifiers_of(&events),
events,
..super::input()
};
self.ctx.run(input, |ctx| {
render(
ctx,
&mut world.model,
&mut world.state,
&self.lernie,
&self.bl,
&self.bz,
);
})
}
/// The right edge of the §11 conversation panel — the boundary between the
/// list column and the centre, read off the panel's own stored rect (the
/// same read `super::geometry` makes of it).
///
/// A drive that asserts a row is **absent** needs it. The altitude-1 descent
/// tree paints the same member names in the centre whenever a conversation
/// is selected, and every gesture under test here selects one, so "nothing
/// paints this name" is a claim about the column and never about the window
/// — asserted window-wide it would fail on a correct tree, and its inverse
/// would pass on one that never unfolded anything.
pub(super) fn column(&self) -> f32 {
egui::containers::panel::PanelState::load(&self.ctx, egui::Id::new("conversations"))
.expect("the conversation panel stores its rect")
.rect
.right()
}
/// Which widget holds the frame's own focus — the §11 floor's cursor, the
/// one Tab moves and Space presses (bl-478d).
pub(super) fn focused(&self) -> Option<egui::Id> {
self.ctx.memory(egui::Memory::focused)
}
/// A frame with no input.
pub(super) fn idle(&self, world: &mut World) -> bool {
self.frame(world, Vec::new())
}
/// Escape — egui spends it surrendering text focus (§11), which is how a
/// test puts the keyboard back down before asking whether an operation
/// picks it up again.
pub(super) fn release(&self, world: &mut World) {
assert!(
!self.frame(world, vec![press(egui::Key::Escape, egui::Modifiers::NONE)]),
"Escape is the release gesture: the box must let go"
);
}
}
/// The modifier plane a frame's presses arrive on.
fn modifiers_of(events: &[egui::Event]) -> egui::Modifiers {
events
.iter()
.find_map(|e| match e {
egui::Event::Key { modifiers, .. } => Some(*modifiers),
_ => None,
})
.unwrap_or(egui::Modifiers::NONE)
}
/// The §11 Ctrl+Shift plane (⌘⇧ on macOS) — the `new workspace` combo.
pub(super) fn command_shift() -> egui::Modifiers {
egui::Modifiers {
shift: true,
..egui::Modifiers::COMMAND
}
}
/// One full click at `pos`: move, press, release — three frames, because egui
/// hit-tests against the *previous* frame's widget rects, so a press in the
/// frame that first sees the pointer would test against nothing.
pub(super) fn click(screen: &Screen, world: &mut World, pos: egui::Pos2) {
screen.frame(world, vec![egui::Event::PointerMoved(pos)]);
for pressed in [true, false] {
screen.frame(
world,
vec![egui::Event::PointerButton {
pos,
button: egui::PointerButton::Primary,
pressed,
modifiers: egui::Modifiers::NONE,
}],
);
}
}
/// The rect of the galley reading exactly `text` — how a pointer test names a
/// seat without an `egui::Id` it has no way to know, and how a geometry test
/// asks which panel a row landed in.
pub(super) fn rect_of(shapes: &[egui::epaint::ClippedShape], text: &str) -> Option<egui::Rect> {
shapes.iter().find_map(|clipped| find(&clipped.shape, text))
}
/// The centre of that rect — the coordinate a click is aimed at.
pub(super) fn locate(shapes: &[egui::epaint::ClippedShape], text: &str) -> Option<egui::Pos2> {
rect_of(shapes, text).map(|r| r.center())
}
fn find(shape: &egui::Shape, text: &str) -> Option<egui::Rect> {
match shape {
egui::Shape::Text(t) if t.galley.text() == text => {
Some(t.galley.rect.translate(t.pos.to_vec2()))
}
egui::Shape::Vec(shapes) => shapes.iter().find_map(|s| find(s, text)),
_ => None,
}
}
pub(super) fn press(key: egui::Key, modifiers: egui::Modifiers) -> egui::Event {
egui::Event::Key {
key,
physical_key: None,
pressed: true,
repeat: false,
modifiers,
}
}