Check the LayoutGeometry case list for completeness (improvise-26dm)

assert_eq!(cases.len(), 13) catches a case being DELETED from the list and
never one being ABSENT from it, and the two are written identically as an
assert_eq over a number. The adversarial review of PR #78 added a fourteenth
pub struct declaring LayoutGeometry, pointed at by nothing, and the suite
stayed green. doctrine(a_list_checked_for_length_reads_like_a_list_checked_
for_completeness).

improvise-5ubs's mechanism cannot reach this one -- an effect is a struct
implementing a trait, so serde has no derived enumeration to diff against. The
WIRE vocabulary does have one, and model_effects maps each ModelCommand variant
back to the effect it came from (gotcha 38), so the list is now diffed against
the LayoutGeometry effects reachable through ModelCommand. The two sets are
equal today, which is the evidence the substitution lost nothing. Injection:
drop TogglePruneEmpty -- the old count said 12 != 13, this names the effect.

reduce.rs's test module, every_model_command and effect_type_name become
pub(crate) so the enumeration and the name reader have ONE home rather than a
second copy in effect.rs.

Residual, stated in the evidence rather than closed: total over the wire, not
over the effects. An effect no ModelCommand names is still invisible -- exactly
PR #78's probe. A second [...; 13] array survives one function away in
every_layout_geometry_effect_is_visible_to_the_narrow_fingerprint; filed, not
smuggled in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-21 10:19:20 -07:00
parent 78ca25b5e0
commit 019cbc1f72
2 changed files with 40 additions and 11 deletions
+37 -8
View File
@@ -1410,11 +1410,36 @@ mod tests {
}
}
/// Effects declaring `LayoutGeometry` must move `document_config` — the
/// production hash `model_state.dirty` is decided by (improvise-1xf4).
/// The case list below is COMPLETE over the wire vocabulary, which the
/// compiler maintains — improvise-26dm, `context/code/command.md`.
#[test]
fn layout_geometry_effects_actually_change_document_config() {
let cases: Vec<Box<dyn Effect>> = vec![
fn the_layout_geometry_cases_are_every_wire_effect_of_that_kind() {
let app = grouped_app();
let mut reachable: Vec<String> = crate::ui::reduce::tests::every_model_command()
.iter()
.flat_map(|cmd| crate::ui::reduce::model_effects(&app, cmd))
.filter(|e| e.kind() == EffectKind::Model(ModelChange::LayoutGeometry))
.map(|e| crate::ui::reduce::tests::effect_type_name(e.as_ref()))
.collect();
let mut listed: Vec<String> = layout_geometry_cases()
.iter()
.map(|e| crate::ui::reduce::tests::effect_type_name(e.as_ref()))
.collect();
reachable.sort_unstable();
reachable.dedup();
listed.sort_unstable();
listed.dedup();
assert_eq!(
listed, reachable,
"the LayoutGeometry case list and the wire vocabulary disagree; a name \
only on the right is an effect nothing here exercises"
);
}
/// The `LayoutGeometry` effects, named once so two tests can ask the same
/// list the same questions — improvise-26dm, `context/code/command.md`.
fn layout_geometry_cases() -> Vec<Box<dyn Effect>> {
vec![
Box::new(AddCategory("Region".into())),
Box::new(RemoveCategory("Month".into())),
Box::new(CreateView("Second".into())),
@@ -1443,10 +1468,14 @@ mod tests {
Box::new(CycleAxis("Type".into())),
Box::new(SetNumberFormat(",.3f".into())),
Box::new(TogglePruneEmpty),
];
// A COUNT, not a totality check: nothing forces a fourteenth here, and a
// probe variant proved it — improvise-5ubs, `context/code/command.md`.
assert_eq!(cases.len(), 13, "the LayoutGeometry effects were thirteen");
]
}
/// Effects declaring `LayoutGeometry` must move `document_config` — the
/// production hash `model_state.dirty` is decided by (improvise-1xf4).
#[test]
fn layout_geometry_effects_actually_change_document_config() {
let cases = layout_geometry_cases();
for effect in cases {
assert_eq!(
effect.kind(),
+3 -3
View File
@@ -273,7 +273,7 @@ pub fn reduce_key(app: &mut App, key: KeyEvent) -> Vec<Command> {
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use super::*;
use crate::model::cell::CellValue;
use crate::ui::app::AppMode;
@@ -494,7 +494,7 @@ mod tests {
/// One value per `ModelCommand` variant. [`variant_name`] matches
/// exhaustively, so a new variant fails the build until it is listed here.
fn every_model_command() -> Vec<ModelCommand> {
pub(crate) fn every_model_command() -> Vec<ModelCommand> {
vec![
ModelCommand::SetCell {
cell: record_cell(&a_stored_cell()),
@@ -658,7 +658,7 @@ mod tests {
/// An effect's type name, off the leading identifier of its derived `Debug`.
/// `Effect` is a trait object, so `std::any::type_name` cannot reach it.
fn effect_type_name(effect: &dyn Effect) -> String {
pub(crate) fn effect_type_name(effect: &dyn Effect) -> String {
format!("{effect:?}")
.split(|c: char| !c.is_alphanumeric() && c != '_')
.next()