Files
TerminalBasic/openspec/changes/archive/2026-09-05-ereigniszustellung-und-hostgrenzen/review-probes.rs

168 lines
6.1 KiB
Rust

//! Ursprüngliche Reproduktionen: fünf behobene Befunde und eine Kontrollprobe.
//! Separater Cargo-Treiber; siehe review.md. Alle sechs Prüfungen müssen bestehen.
use tb_frontend::forms::{FormCatalog, ObjectClass};
use tb_runtime::host::{CaptureHost, Ereignis, Host, MausArt, MausEreignis};
use tb_runtime::screen::TextScreen;
use tb_runtime::value::Value;
use tb_vm::interp::{RunEvent, Vm};
fn vm(src: &str) -> Vm {
let mut catalog = FormCatalog::default();
catalog.add("Form1", ObjectClass::Form, None, false);
catalog.add("Text1", ObjectClass::TextBox, Some("Form1"), false);
catalog.add("Text2", ObjectClass::TextBox, Some("Form1"), false);
catalog.add("Timer1", ObjectClass::Timer, Some("Form1"), false);
catalog.add("Menu1", ObjectClass::Menu, Some("Form1"), false);
catalog.add("Item1", ObjectClass::Menu, Some("Menu1"), false);
Vm::new(
tb_vm::compile_source_with_forms("FORM1", src, &catalog)
.unwrap_or_else(|d| panic!("{d:?}")),
)
}
fn stop(vm: &mut Vm, host: &mut dyn Host) {
assert!(matches!(vm.run(host), RunEvent::Stopped { .. }));
}
fn string(vm: &Vm, name: &str) -> String {
match vm.inspect(name) {
Some(Value::Str(s)) => s.to_string(),
other => panic!("{other:?}"),
}
}
fn key(s: &str) -> Ereignis {
Ereignis::Taste(s.into(), 0)
}
fn mouse() -> Ereignis {
Ereignis::Maus(MausEreignis {
art: MausArt::Druck,
taste: 1,
shift: 0,
zeile: 1,
spalte: 1,
})
}
#[test]
fn review_signal_waehrend_blockierender_eingabe() {
struct Input {
reads: usize,
}
impl Host for Input {
fn present(&mut self, _: &TextScreen) {}
fn next_event(&mut self, block: bool) -> Option<Ereignis> {
if !block {
return None;
}
self.reads += 1;
Some(if self.reads == 1 {
Ereignis::Signal(2)
} else {
Ereignis::Ende
})
}
fn jetzt_ms(&mut self) -> u64 {
0
}
fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
self.next_event(true)
}
}
let mut results = vec![];
for statement in ["INPUT s$", "LINE INPUT s$", "s$ = INPUT$(1)"] {
let src =
format!("ON SIGNAL(2) GOSUB H\nSIGNAL(2) ON\n{statement}\nEND\nH:\nPRINT \"H\"\nEND");
let mut vm = Vm::new(tb_vm::compile_source("TEST", &src).unwrap());
let result = vm.run(&mut Input { reads: 0 });
println!(
"{statement}: {result:?}; Ausgabe={:?}",
tb_runtime::snapshot::text(&vm.rt.screen)
);
results.push(result);
}
assert!(results.iter().all(|result| *result == RunEvent::Ended));
}
#[test]
fn review_objektargument_darf_gotfocus_nicht_vorziehen() {
let mut vm = vm("DIM SHARED s$\nText1.TabIndex = 0\nText2.TabIndex = 1\nForm1.Show\nText1.SetFocus\nSTOP\nDOEVENTS\nEND\nSUB Text1_LostFocus()\nSHARED s$\nCALL Mark(Text1)\ns$ = s$ + \"l\"\nEND SUB\nSUB Mark(c AS CONTROL)\nSHARED s$\ns$ = s$ + \"L\"\nEND SUB\nSUB Text2_GotFocus()\nSHARED s$\ns$ = s$ + \"G\"\ns$ = s$ + \"g\"\nEND SUB");
let mut host = CaptureHost::default();
stop(&mut vm, &mut host);
host.ereignis(key("\t"));
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(string(&vm, "s"), "LlGg");
}
#[test]
fn review_gleichzeitig_key_und_uevent_behalten_prioritaet() {
let mut vm = vm("ON KEY(15) GOSUB K\nKEY 15, CHR$(0) + CHR$(20)\nKEY(15) ON\nON UEVENT GOSUB U\nUEVENT ON\nSTOP\nDOEVENTS\nEND\nK:\ns$ = s$ + \"K\"\nRETURN\nU:\ns$ = s$ + \"U\"\nRETURN");
let mut host = CaptureHost::default();
stop(&mut vm, &mut host);
host.ereignis(key("t"));
vm.rt.pump(&mut host, false);
vm.rt.traps.melden(tb_runtime::traps::Quelle::UEvent);
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(string(&vm, "s"), "KU");
}
#[test]
fn review_modale_eingabe_vor_eof_wird_zugestellt() {
let mut vm = vm("DIM SHARED s$\nForm1.Show 1\nEND\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = \"M\"\nUNLOAD Form1\nEND SUB");
let mut host = CaptureHost::default();
host.ereignis_nach(2, mouse());
host.ereignis_nach(2, Ereignis::Ende);
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(string(&vm, "s"), "M");
}
#[test]
fn review_menu_schliessen_stellt_pending_trap_vor_erneutem_warten_zu() {
struct MenuHost {
time: u64,
waits: usize,
}
impl Host for MenuHost {
fn present(&mut self, _: &TextScreen) {}
fn next_event(&mut self, _: bool) -> Option<Ereignis> {
None
}
fn jetzt_ms(&mut self) -> u64 {
self.time
}
fn warten(&mut self, deadline: Option<u64>) -> Option<Ereignis> {
self.waits += 1;
if self.waits == 1 {
return Some(key("\u{1b}"));
}
self.time = deadline.unwrap();
None
}
}
let mut vm = vm("ON UEVENT GOSUB H\nUEVENT ON\nMenu1.Caption = \"&Datei\"\nItem1.Caption = \"&Ende\"\nForm1.Show\nSTOP\nSLEEP 60\nEND\nH:\ns$ = \"U\"\nRETURN");
let mut host = CaptureHost::default();
stop(&mut vm, &mut host);
vm.forms.handle_key("d", 4);
vm.rt.traps.melden(tb_runtime::traps::Quelle::UEvent);
let mut host = MenuHost { time: 0, waits: 0 };
assert_eq!(vm.run(&mut host), RunEvent::Ended);
println!(
"Menü/SLEEP: Zeit={}, Warteaufrufe={}, Handler={}",
host.time,
host.waits,
string(&vm, "s")
);
assert_eq!(host.waits, 1);
}
#[test]
fn review_maus_vor_getrappter_taste() {
let mut vm = vm("DIM SHARED s$\nON KEY(15) GOSUB H\nKEY 15, CHR$(0) + CHR$(20)\nKEY(15) ON\nForm1.Show\nSTOP\nDOEVENTS\nDOEVENTS\nEND\nH:\ns$ = s$ + \"K\"\nRETURN\nSUB Form_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)\nSHARED s$\ns$ = s$ + \"M\"\nEND SUB");
let mut host = CaptureHost::default();
stop(&mut vm, &mut host);
host.ereignis(mouse());
host.ereignis(key("t"));
assert_eq!(vm.run(&mut host), RunEvent::Ended);
assert_eq!(string(&vm, "s"), "MK");
}