use newt_interaction::{
plan_presentation, InteractionDefinition, ProtocolError, Requirement, SurfaceFeature,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fallback {
text: String,
unmet_optional: Vec<String>,
}
impl Fallback {
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub fn unmet_optional(&self) -> &[String] {
&self.unmet_optional
}
#[must_use]
pub fn is_faithful(&self) -> bool {
self.unmet_optional.is_empty()
}
}
pub fn present(
definition: &InteractionDefinition,
supported: &[SurfaceFeature],
) -> Result<Fallback, ProtocolError> {
let presentation = plan_presentation(definition, supported)?;
Ok(Fallback {
text: super::plain::render(definition),
unmet_optional: presentation
.degradations()
.iter()
.map(|d| d.feature().to_string())
.collect(),
})
}
#[must_use]
pub fn has_unsatisfiable_requirement(
definition: &InteractionDefinition,
supported: &[SurfaceFeature],
) -> bool {
plan_presentation(definition, supported).is_err()
}
#[must_use]
pub fn headless_capabilities() -> Vec<SurfaceFeature> {
Vec::new()
}
#[must_use]
pub fn expects_a_response(definition: &InteractionDefinition) -> bool {
definition
.controls
.iter()
.any(|c| c.requirement == Requirement::Required)
}
#[cfg(test)]
mod c0b {
use super::*;
use newt_interaction::{
ChoiceOption, Control, ControlId, ControlKind, FeatureDemand, InteractionKind, OptionId,
SemanticRole,
};
fn control(id: &str, kind: ControlKind, label: &str, requirement: Requirement) -> Control {
Control {
id: ControlId::new(id).expect("valid control id"),
kind,
label: label.to_string(),
requirement,
}
}
fn notice() -> InteractionDefinition {
InteractionDefinition::new(InteractionKind::Notice, "the build finished", Vec::new())
}
fn authored_deny() -> InteractionDefinition {
InteractionDefinition::new(
InteractionKind::Choice,
"proceed?",
vec![control(
"decision",
ControlKind::Choice {
options: vec![ChoiceOption {
id: OptionId::new("deny").expect("valid option id"),
role: SemanticRole::Deny,
label: "deny".to_string(),
key: "d".to_string(),
aliases: Vec::new(),
}],
},
"",
Requirement::Required,
)],
)
}
#[test]
fn headless_never_synthesizes_a_response_from_an_authored_role() {
let definition = authored_deny();
let fallback = present(&definition, &headless_capabilities()).expect("presentable");
assert_eq!(fallback.text(), "proceed?\n[d]eny");
assert!(fallback.is_faithful());
assert!(
expects_a_response(&definition),
"the fixture must be one that expects an answer, or this proves nothing"
);
let mut allow = authored_deny();
let ControlKind::Choice { options } = &mut allow.controls[0].kind else {
panic!("fixture is a choice");
};
options[0].role = SemanticRole::Allow;
let allowed = present(&allow, &headless_capabilities()).expect("presentable");
assert_eq!(
allowed.unmet_optional(),
fallback.unmet_optional(),
"the authored role changed the headless outcome"
);
}
#[test]
fn the_headless_module_cannot_build_a_response() {
let source = include_str!("headless.rs");
let production = source
.split("#[cfg(test)]")
.next()
.expect("the production half");
for forbidden in ["Response {", "Response::", "Submission {", "Submission::"] {
assert!(
!production.contains(forbidden),
"the headless module names `{forbidden}` — fail-closed means it \
cannot build a response, not that it chooses not to"
);
}
let seeded = "fn f() { Response::new(x) }";
assert!(
["Response {", "Response::"]
.iter()
.any(|n| seeded.contains(n)),
"the needle set cannot see a response constructor at all"
);
}
#[test]
fn headless_refuses_an_unsupported_required_control() {
let definition = InteractionDefinition::new(
InteractionKind::Form,
"credentials",
vec![control(
"key",
ControlKind::Secret,
"API key",
Requirement::Required,
)],
);
assert!(has_unsatisfiable_requirement(
&definition,
&headless_capabilities()
));
let err = present(&definition, &headless_capabilities()).expect_err("must fail closed");
assert!(
matches!(err, ProtocolError::UnsupportedFeature { ref feature, .. }
if feature == SurfaceFeature::SECRET_INPUT),
"unexpected refusal: {err:?}"
);
let capable = [SurfaceFeature::new(SurfaceFeature::SECRET_INPUT).expect("valid")];
let ok = present(&definition, &capable).expect("a capable surface presents it");
assert_eq!(ok.text(), "credentials\nAPI key: (secret, not echoed)");
}
#[test]
fn an_unmet_optional_demand_degrades_visibly() {
let mut definition = notice();
definition.features.push(FeatureDemand {
feature: SurfaceFeature::new(SurfaceFeature::DIAGRAMS).expect("valid"),
requirement: Requirement::Optional,
});
let fallback = present(&definition, &headless_capabilities()).expect("presents");
assert_eq!(fallback.unmet_optional(), [SurfaceFeature::DIAGRAMS]);
assert!(
!fallback.is_faithful(),
"a dropped diagram must be visible, not inferred from an empty list"
);
assert_eq!(fallback.text(), "the build finished");
}
#[test]
fn a_notice_is_presentable_and_expects_no_response() {
let definition = notice();
assert!(!expects_a_response(&definition));
let fallback = present(&definition, &headless_capabilities()).expect("presents");
assert_eq!(fallback.text(), "the build finished");
assert!(fallback.is_faithful());
}
#[test]
fn only_a_required_control_makes_a_definition_expect_an_answer() {
let optional = InteractionDefinition::new(
InteractionKind::Form,
"notes",
vec![control(
"note",
ControlKind::Text,
"note",
Requirement::Optional,
)],
);
assert!(!expects_a_response(&optional));
let required = InteractionDefinition::new(
InteractionKind::Form,
"notes",
vec![control(
"note",
ControlKind::Text,
"note",
Requirement::Required,
)],
);
assert!(expects_a_response(&required));
}
#[test]
fn the_headless_path_never_waits_on_a_terminal() {
let production = code_without_comments(
include_str!("headless.rs")
.split("#[cfg(test)]")
.next()
.expect("the production half"),
);
for waiting in ["PromptWindow", "stdin", "is_terminal", "read_line"] {
assert!(
!production.contains(waiting),
"the headless module reaches `{waiting}` — headless must never \
wait for input it has nobody to receive"
);
}
assert!(
production.contains("plan_presentation"),
"the scan is not reading this module's code at all"
);
}
#[test]
fn the_no_waiting_scan_sees_a_real_terminal_read() {
let seeded = code_without_comments(
"// a comment mentioning PromptWindow and stdin is not a hit\n\
pub fn f() { let w = PromptWindow::x(); }\n",
);
assert!(
seeded.contains("PromptWindow"),
"the scan cannot see a real terminal reference"
);
let only_prose = code_without_comments(
"// PromptWindow, stdin, is_terminal, read_line\npub fn f() {}\n",
);
for waiting in ["PromptWindow", "stdin", "is_terminal", "read_line"] {
assert!(
!only_prose.contains(waiting),
"prose was counted as code: `{waiting}`"
);
}
}
fn code_without_comments(source: &str) -> String {
source
.lines()
.filter(|l| !l.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn a_headless_surface_advertises_no_capability() {
assert!(headless_capabilities().is_empty());
}
}