126 lines
5.2 KiB
Rust
126 lines
5.2 KiB
Rust
//! A parameter whose value is lines of text must be declared so, or the editor offers it in
|
|
//! a one-line field — and a browser strips the newlines from a one-line field's value.
|
|
//!
|
|
//! That is how a statechart edited in the inspector came back as a single line: a chart that
|
|
//! no longer parses, so the node quietly passed its state through and nothing said why.
|
|
//! `Widget::multiline_parameters` is the declaration; these tests hold the library to it.
|
|
use flowrs_core::graph::value::Value;
|
|
use flowrs_core::graph::widget::{ExecContext, WidgetRegistry, edits_as_lines};
|
|
use std::collections::HashMap;
|
|
|
|
#[test]
|
|
fn every_parameter_whose_default_spans_lines_is_declared() {
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let mut spanning = 0;
|
|
let mut undeclared = Vec::new();
|
|
for w in reg.all() {
|
|
for p in w.parameters() {
|
|
if p.default.contains(['\n', '\r']) {
|
|
spanning += 1;
|
|
if !w.multiline_parameters().contains(&p.name.as_str()) {
|
|
undeclared.push(format!("{}.{}", w.id(), p.name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// A guard over an empty set passes and proves nothing. The statechart's chart and the
|
|
// inline table's CSV both default to several lines.
|
|
assert!(spanning >= 2, "found only {spanning} multi-line defaults");
|
|
assert!(
|
|
undeclared.is_empty(),
|
|
"these default to several lines but would be edited in a one-line field, which \
|
|
drops the line breaks: {undeclared:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn every_declared_name_is_a_parameter_of_its_widget() {
|
|
// The declaration is a list of names beside `parameters()`, so a rename on one side
|
|
// leaves the other pointing at nothing — and the parameter quietly back in a one-line
|
|
// field.
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let mut declared = 0;
|
|
let mut dangling = Vec::new();
|
|
for w in reg.all() {
|
|
let names: Vec<String> = w.parameters().into_iter().map(|p| p.name).collect();
|
|
for name in w.multiline_parameters() {
|
|
declared += 1;
|
|
if !names.iter().any(|n| n == name) {
|
|
dangling.push(format!("{}.{name}", w.id()));
|
|
}
|
|
}
|
|
}
|
|
assert!(declared >= 5, "found only {declared} declarations");
|
|
assert!(
|
|
dangling.is_empty(),
|
|
"declared multi-line but not a parameter: {dangling:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn line_oriented_parameters_are_edited_as_lines_before_they_hold_anything() {
|
|
// Each of these is lines by its format, not by its current value: a statechart, CSV,
|
|
// a bundle written one question per line, and prompts, which are prose.
|
|
let reg = WidgetRegistry::with_builtins();
|
|
for (id, name) in [
|
|
("flow.statechart", "machine"),
|
|
("data.table", "csv"),
|
|
("ai.hybrid", "bundle"),
|
|
("ai.hybrid", "prompt"),
|
|
("ai.complete", "prompt"),
|
|
] {
|
|
let w = reg
|
|
.get(id)
|
|
.unwrap_or_else(|| panic!("{id} is not registered"));
|
|
assert!(edits_as_lines(Some(w), name, ""), "{id}.{name}");
|
|
}
|
|
// A one-line parameter stays one line.
|
|
let print = reg.get("io.print").expect("io.print");
|
|
assert!(!edits_as_lines(Some(print), "label", "total"));
|
|
let hybrid = reg.get("ai.hybrid").expect("ai.hybrid");
|
|
assert!(!edits_as_lines(Some(hybrid), "options", "yes, no"));
|
|
}
|
|
|
|
#[test]
|
|
fn a_value_holding_a_line_break_is_edited_as_lines_whatever_was_declared() {
|
|
// Whoever wrote the break (a file, an agent, an importer) meant it, and a one-line
|
|
// field would delete it on the next edit. Code nodes from a Rust import are not
|
|
// registry widgets at all, and their `src` is routinely several lines.
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let print = reg.get("io.print").expect("io.print");
|
|
assert!(edits_as_lines(Some(print), "label", "first\nsecond"));
|
|
assert!(edits_as_lines(None, "src", "fn f() {\n 1\n}"));
|
|
assert!(edits_as_lines(None, "src", "old\rmac"));
|
|
assert!(!edits_as_lines(None, "src", "x + 1"));
|
|
}
|
|
|
|
/// What is at stake, shown on the widget that motivated this: the same chart, flattened the
|
|
/// way a one-line field flattens it, does not run.
|
|
#[test]
|
|
fn a_statechart_runs_its_added_lines_and_not_once_flattened() {
|
|
let reg = WidgetRegistry::with_builtins();
|
|
let chart = reg.get("flow.statechart").expect("flow.statechart");
|
|
let state_of = |machine: &str| {
|
|
let props = HashMap::from([("machine".to_string(), machine.to_string())]);
|
|
let out = chart.execute(&ExecContext::for_widget(chart, &props), HashMap::new());
|
|
match out.get("state") {
|
|
Some(Value::String(s)) => s.clone(),
|
|
other => panic!("expected a state, got {other:?}"),
|
|
}
|
|
};
|
|
let default = chart
|
|
.parameters()
|
|
.into_iter()
|
|
.find(|p| p.name == "machine")
|
|
.expect("machine")
|
|
.default;
|
|
assert_eq!(state_of(&default), "off");
|
|
|
|
// A state and an eventless transition into it, added as two more lines.
|
|
let edited = format!("{default}state: lit\nalways: off -> lit\n");
|
|
assert_eq!(state_of(&edited), "lit");
|
|
|
|
// What an <input> hands back: every line break gone.
|
|
let flattened = edited.replace('\n', "");
|
|
assert_ne!(state_of(&flattened), "lit");
|
|
}
|