test(dired): teach describe.key about mode scope; drop one overclaim
`describe_key_identifies_every_default_binding` iterated every binding in the stack and asserted `pmacs.describe.key` resolves it context-free. That held only because no builtin had ever bound a mode-scoped key: dired is #129's first non-detection consumer, so its `n` / `p` / `g` correctly resolved to nothing and the test went red on the feature rather than on a defect. It now sets the effective context per binding -- the mode for a mode-scoped default, and explicitly NO mode for a global one, because a leaked mode legitimately shadows a global chord of the same name (dired's `RET` shadows `edit.newline-and-indent`, which is the point of the mode) and would make the assertion compare the wrong pair. A floor assertion keeps the new arm from going vacuous if the last mode-scoped default is ever removed. Also corrects a doc comment rather than leaving it to be believed: acceptance 3c does not pin the descent ROUTING. Dired holds focus in its own panel, so a raw `switch_buffer` lands in the same window and the mutation is vacuous against that test; dedication is what distinguishes the two paths, so the discriminating pin is the dedicated-panel test next to it. Verified by mutation, not assumed.
This commit is contained in:
parent
f71055a206
commit
e7fa9e9720
|
|
@ -5659,17 +5659,25 @@ mod tests {
|
||||||
|
|
||||||
// ---- T M2.11 acceptance --------------------------------------------------
|
// ---- T M2.11 acceptance --------------------------------------------------
|
||||||
|
|
||||||
/// Every chord in the default global keymap must round-trip through
|
/// Every chord in the default keymap must round-trip through
|
||||||
/// `pmacs.describe.key`: returning a non-nil table whose `command`
|
/// `pmacs.describe.key`: returning a non-nil table whose `command`
|
||||||
/// matches the binding the keymap stack stores.
|
/// matches the binding the keymap stack stores.
|
||||||
|
///
|
||||||
|
/// `describe.key` resolves against the **effective context**
|
||||||
|
/// (buffer-local → mode → global), so a mode-scoped default is
|
||||||
|
/// asserted with a buffer that carries that mode rather than
|
||||||
|
/// context-free. Dired is the first builtin to bind mode-scoped keys
|
||||||
|
/// (#129's first non-detection consumer), and without the mode in
|
||||||
|
/// place its `n` / `p` / `g` correctly resolve to nothing.
|
||||||
#[test]
|
#[test]
|
||||||
fn describe_key_identifies_every_default_binding() {
|
fn describe_key_identifies_every_default_binding() {
|
||||||
|
use crate::keymap_stack::Scope;
|
||||||
let s = EditorState::new();
|
let s = EditorState::new();
|
||||||
let kms = s.lua_host.keymaps().borrow();
|
let kms = s.lua_host.keymaps().borrow();
|
||||||
let bindings: Vec<(String, String)> = kms
|
let bindings: Vec<(Scope, String, String)> = kms
|
||||||
.iter_all()
|
.iter_all()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, seq, b)| (crate::key::display_sequence(&seq), b.command))
|
.map(|(scope, seq, b)| (scope, crate::key::display_sequence(&seq), b.command))
|
||||||
.collect();
|
.collect();
|
||||||
drop(kms);
|
drop(kms);
|
||||||
// Sanity floor: the default keymap binds at least the M1 surface.
|
// Sanity floor: the default keymap binds at least the M1 surface.
|
||||||
|
|
@ -5678,18 +5686,50 @@ mod tests {
|
||||||
"default keymap unexpectedly small: {} bindings",
|
"default keymap unexpectedly small: {} bindings",
|
||||||
bindings.len()
|
bindings.len()
|
||||||
);
|
);
|
||||||
|
let modes: usize = bindings
|
||||||
|
.iter()
|
||||||
|
.filter(|(scope, _, _)| matches!(scope, Scope::Mode(_)))
|
||||||
|
.count();
|
||||||
|
assert!(
|
||||||
|
modes >= 1,
|
||||||
|
"a mode-scoped default is expected since dired Stage 1; \
|
||||||
|
found none, so the mode arm below asserts nothing"
|
||||||
|
);
|
||||||
|
|
||||||
for (seq, expected_command) in &bindings {
|
for (scope, seq, expected_command) in &bindings {
|
||||||
|
let mode = match scope {
|
||||||
|
Scope::Mode(name) => Some(name.clone()),
|
||||||
|
// No buffer-scoped defaults exist; a future one would
|
||||||
|
// need its own buffer context here.
|
||||||
|
Scope::Buffer(_) => continue,
|
||||||
|
Scope::Global => None,
|
||||||
|
};
|
||||||
|
// Set the context explicitly on EVERY iteration, including
|
||||||
|
// the global one: a mode left over from a previous iteration
|
||||||
|
// legitimately shadows a global binding of the same chord
|
||||||
|
// (dired's mode-scoped `RET` shadows
|
||||||
|
// `edit.newline-and-indent`, which is the point of the
|
||||||
|
// mode), so a leaked mode would make this assert the wrong
|
||||||
|
// thing.
|
||||||
|
let context = match &mode {
|
||||||
|
Some(name) => {
|
||||||
|
format!("pmacs.buffer.set_major_mode(pmacs.window.buffer(), {name:?}); ")
|
||||||
|
}
|
||||||
|
None => "pmacs.buffer.set_major_mode(pmacs.window.buffer(), nil); ".to_owned(),
|
||||||
|
};
|
||||||
let script = format!(
|
let script = format!(
|
||||||
"local r = pmacs.describe.key({seq:?}); \
|
"{context}local r = pmacs.describe.key({seq:?}); \
|
||||||
if r == nil then return 'nil' else return r.command end"
|
if r == nil then return 'nil' else return r.command end"
|
||||||
);
|
);
|
||||||
let got: String = s.lua_host.lua().load(&script).eval().unwrap_or_else(|e| {
|
let got: String = s.lua_host.lua().load(&script).eval().unwrap_or_else(|e| {
|
||||||
panic!("describe.key({seq}) raised: {e}");
|
panic!("describe.key({seq}) raised: {e}");
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&got, expected_command,
|
&got,
|
||||||
"describe.key for {seq:?} returned {got:?}, expected {expected_command:?}"
|
expected_command,
|
||||||
|
"describe.key for {seq:?} (scope {}) returned {got:?}, \
|
||||||
|
expected {expected_command:?}",
|
||||||
|
scope.render()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -700,6 +700,14 @@ fn dired_canonicalization_is_the_cores_own_normalizer() {
|
||||||
/// side window (Q#DR10): the next directory is the same kind of thing as
|
/// side window (Q#DR10): the next directory is the same kind of thing as
|
||||||
/// the current one and belongs in the same slot. Neither replaced by a
|
/// the current one and belongs in the same slot. Neither replaced by a
|
||||||
/// document window nor duplicated.
|
/// document window nor duplicated.
|
||||||
|
///
|
||||||
|
/// **This test does not pin the routing itself, and says so rather than
|
||||||
|
/// implying otherwise:** dired holds the focus in its own panel here, so
|
||||||
|
/// a raw `switch_buffer` lands in that same window and the assertions
|
||||||
|
/// below hold either way (verified — the mutation is VACUOUS against
|
||||||
|
/// this test). What distinguishes `display { side = … }` from the raw
|
||||||
|
/// switch is dedication, so the discriminating pin is
|
||||||
|
/// `dired_descent_from_a_dedicated_panel_leaves_the_pin_alone` below.
|
||||||
#[test]
|
#[test]
|
||||||
fn dired_directory_descent_stays_in_its_side_window() {
|
fn dired_directory_descent_stays_in_its_side_window() {
|
||||||
let td = fixture_dir();
|
let td = fixture_dir();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue