117 lines
4.7 KiB
Rust
117 lines
4.7 KiB
Rust
//! What a stored parameter value MEANS must be the same to the validator, to T0 and to
|
|
//! the generated program.
|
|
//!
|
|
//! Two places disagreed. `check_parameters` trimmed a value before checking it, so `" 1"`
|
|
//! was a valid number — and every reader parsed it untrimmed, failed, and quietly used the
|
|
//! default. And `check_parameters` read an empty value as "use the default", while the
|
|
//! editor stored `""` on a cleared field and the readers took `""` as the value.
|
|
|
|
use flowrs_core::graph::diagnostics::check_parameters;
|
|
use flowrs_core::graph::executor::GraphExecutor;
|
|
use flowrs_core::graph::value::Value;
|
|
use flowrs_core::graph::widget::{ExecContext, WidgetRegistry, stored_value};
|
|
use flowrs_core::{Graph, NodeId};
|
|
use std::collections::HashMap;
|
|
|
|
/// An `ai.complete` whose temperature is `temperature`. With nothing to answer it, the
|
|
/// local completer replies, and a non-zero temperature rotates the prompt's words — so
|
|
/// the temperature the node actually read is visible in its output.
|
|
fn complete_at(temperature: &str) -> (Graph, NodeId) {
|
|
let mut g = Graph::new();
|
|
let id = g.add_node("ai.complete", 0.0, 0.0);
|
|
let props = &mut g.get_node_mut(id).unwrap().properties;
|
|
props.insert("prompt".into(), "alpha beta gamma delta".into());
|
|
props.insert("temperature".into(), temperature.into());
|
|
(g, id)
|
|
}
|
|
|
|
fn t0_text(g: &Graph, id: NodeId) -> String {
|
|
let mut ex = GraphExecutor::new(WidgetRegistry::with_builtins());
|
|
let steps = ex.run(g).expect("runs");
|
|
match steps
|
|
.iter()
|
|
.find(|s| s.node_id == id)
|
|
.and_then(|s| s.outputs.get("text"))
|
|
{
|
|
Some(Value::String(s)) => s.clone(),
|
|
other => panic!("no text output: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_number_with_spaces_around_it_means_the_number_in_both_tiers() {
|
|
let (spaced, a) = complete_at(" 1 ");
|
|
let (plain, b) = complete_at("1");
|
|
let (zero, c) = complete_at("0");
|
|
|
|
// The validator accepts it…
|
|
assert!(check_parameters(&spaced, &WidgetRegistry::with_builtins()).is_empty());
|
|
// …so T0 must read 1, not fall back to 0. (0 and 1 differ, or this proves nothing.)
|
|
assert_ne!(t0_text(&plain, b), t0_text(&zero, c));
|
|
assert_eq!(t0_text(&spaced, a), t0_text(&plain, b));
|
|
// …and so must the generated program.
|
|
assert_eq!(spaced.to_rust().unwrap(), plain.to_rust().unwrap());
|
|
assert_ne!(zero.to_rust().unwrap(), plain.to_rust().unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn free_text_keeps_its_spaces() {
|
|
// A separator of " " is a space, not "nothing": only tokens are trimmed.
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let join = reg.get("text.join").expect("text.join");
|
|
let props = HashMap::from([("separator".to_string(), " ".to_string())]);
|
|
let ctx = ExecContext::for_widget(join, &props);
|
|
assert_eq!(ctx.params["separator"], " ");
|
|
}
|
|
|
|
#[test]
|
|
fn clearing_a_declared_parameter_removes_it_so_the_default_applies() {
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let chart = reg.get("flow.statechart");
|
|
// Cleared: remove, which is what `check_parameters` already assumed "" meant.
|
|
assert_eq!(stored_value(chart, "machine", String::new()), None);
|
|
// Anything else is stored as typed.
|
|
assert_eq!(
|
|
stored_value(chart, "machine", "initial: a\nstate: a\n".into()).as_deref(),
|
|
Some("initial: a\nstate: a\n")
|
|
);
|
|
// A token that is only whitespace is empty too; free text that is only a space is not.
|
|
let complete = reg.get("ai.complete");
|
|
assert_eq!(stored_value(complete, "temperature", " ".into()), None);
|
|
let join = reg.get("text.join");
|
|
assert_eq!(
|
|
stored_value(join, "separator", " ".into()).as_deref(),
|
|
Some(" ")
|
|
);
|
|
// Not a declared parameter (a literal's value, a code node's src): there is no
|
|
// default to fall back to, so an empty value is kept.
|
|
assert_eq!(
|
|
stored_value(None, "src", String::new()).as_deref(),
|
|
Some("")
|
|
);
|
|
assert_eq!(
|
|
stored_value(chart, "label", String::new()).as_deref(),
|
|
Some("")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_removed_statechart_machine_runs_the_default_chart() {
|
|
// The behaviour the removal exists for: no `machine` property is the default chart,
|
|
// where `""` was an empty chart that does not parse.
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let chart = reg.get("flow.statechart").unwrap();
|
|
let state = |props: HashMap<String, String>| match chart
|
|
.execute(&ExecContext::for_widget(chart, &props), HashMap::new())
|
|
.get("state")
|
|
{
|
|
Some(Value::String(s)) => s.clone(),
|
|
other => panic!("{other:?}"),
|
|
};
|
|
assert_eq!(state(HashMap::new()), "off");
|
|
assert_ne!(
|
|
state(HashMap::from([("machine".to_string(), String::new())])),
|
|
"off",
|
|
"an empty chart is not the default one, which is why clearing must remove"
|
|
);
|
|
}
|