932e7464d7
Cierra el ultimo gran TODO de la metainterfaz Nakui: las acciones
Action::Morphism ya no son un toast informativo; despachan al
Executor cargado del manifest nakui-core (nsmc.json + schemas KCL +
scripts Rhai), pasando por el pipeline completo: compute (con
dry-run + KCL post-checks) -> log append -> store apply.
Schema nakui-ui-schema extendido:
- Module.nakui_module_dir: Option<String> nuevo. Path al modulo
nakui-core. Sin esto, Action::Morphism quedan no-op con toast.
SeedEntity sigue funcionando (alta administrativa sin manifest).
- Action::Morphism gano dos campos opcionales:
- inputs: BTreeMap<String, String> — mapeo role -> field_name.
- params: Vec<String> — fields cuyos values van al params JSON.
Si vacio, todos los fields no-input van a params.
Runtime nakui-ui:
- MetaUi.executors: BTreeMap<String, Arc<Executor>> nuevo. Carga
Executor::load_module(nakui_module_dir) en MetaUi::new.
- commit_morphism: resuelve inputs (parsea UUIDs), arma params
(Value object con tipos inferidos), llama
execute_and_log_with_recovery. Toast con count de ops o error.
- infer_param_value: heuristica i64 -> f64 -> bool -> string.
Tests: 2 nuevos. E2E morphism_pipeline_executes_real_sales_vender
carga el modulo real crates/modules/nakui/modules/sales, ejecuta
"vender" con inputs Stock+Caja y params (cantidad=5, precio=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacio).
- stock.cantidad: 100 -> 95.
- caja.saldo: 1_000_000 -> 1_001_000.
12 tests verdes en nakui-ui (+1). Schema extension no rompio nada
(6 unit + 5 integration siguen verdes).
Demo nuevo: examples/nakui-modules/sales_engine/module.json apunta
al sales real via nakui_module_dir. 6 vistas (list+form para Stock/
Caja/Venta + "Vender" con Action::Morphism). El user crea Stocks +
Cajas con seed_entity, copia los UUIDs a los inputs de "Vender", y
ejecuta el morphism real con KCL post-checks.
Activacion:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
Trade-offs:
- Inputs UUID a mano (no dropdown). Nice-to-have: FieldKind::EntityRef
que renderee selector.
- Inferencia de tipo en params es heuristica.
117 lines
3.8 KiB
Rust
117 lines
3.8 KiB
Rust
//! Validación de los 6 módulos demo en `examples/nakui-modules/`.
|
|
//!
|
|
//! Si esto verde, garantizamos que un usuario que clone el repo y
|
|
//! corra `NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui`
|
|
//! va a obtener los 6 módulos cargados sin tocar nada.
|
|
|
|
use nakui_ui_schema::{load_modules_from_dir, FieldKind, View};
|
|
|
|
fn examples_dir() -> std::path::PathBuf {
|
|
// Tests corren desde el dir del crate; el repo root está dos
|
|
// niveles arriba: crates/modules/nakui/ui-schema → repo.
|
|
let here = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
here.join("../../../..").join("examples/nakui-modules")
|
|
}
|
|
|
|
#[test]
|
|
fn loads_all_demo_modules() {
|
|
let dir = examples_dir();
|
|
let mods = load_modules_from_dir(&dir).unwrap_or_else(|e| {
|
|
panic!("load failed for {}: {e}", dir.display());
|
|
});
|
|
let ids: Vec<&str> = mods.iter().map(|m| m.id.as_str()).collect();
|
|
assert_eq!(
|
|
ids,
|
|
vec![
|
|
"customers",
|
|
"inventory_movements",
|
|
"invoices",
|
|
"products",
|
|
"sales_engine",
|
|
"sales_orders",
|
|
"suppliers",
|
|
],
|
|
"expected 7 modules in alphabetical order \
|
|
(sales_engine se sumó al wirear Action::Morphism)"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sales_engine_declares_nakui_module_dir_and_morphism() {
|
|
// Sanity del módulo demo de morphism: nakui_module_dir set,
|
|
// y al menos una vista con Action::Morphism en su on_submit.
|
|
let mods = load_modules_from_dir(examples_dir()).unwrap();
|
|
let sales = mods
|
|
.iter()
|
|
.find(|m| m.id == "sales_engine")
|
|
.expect("sales_engine debe estar");
|
|
assert!(
|
|
sales.nakui_module_dir.is_some(),
|
|
"sales_engine debería declarar nakui_module_dir"
|
|
);
|
|
let has_morphism_view = sales.views.values().any(|v| match v {
|
|
nakui_ui_schema::View::Form(form) => {
|
|
matches!(form.on_submit, nakui_ui_schema::Action::Morphism { .. })
|
|
}
|
|
_ => false,
|
|
});
|
|
assert!(
|
|
has_morphism_view,
|
|
"sales_engine debería tener al menos una Form con Action::Morphism"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn every_demo_module_has_list_and_form_views() {
|
|
let mods = load_modules_from_dir(examples_dir()).unwrap();
|
|
for m in &mods {
|
|
let mut has_list = false;
|
|
let mut has_form = false;
|
|
for v in m.views.values() {
|
|
match v {
|
|
View::List(_) => has_list = true,
|
|
View::Form(_) => has_form = true,
|
|
}
|
|
}
|
|
assert!(
|
|
has_list && has_form,
|
|
"module {} should expose at least one list + one form view",
|
|
m.id
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn every_demo_form_field_kind_is_recognized() {
|
|
// Sanity: ningún módulo demo usa un kind que no esté en el enum
|
|
// (sería rechazado al parsear, pero check explícito no daña).
|
|
let mods = load_modules_from_dir(examples_dir()).unwrap();
|
|
for m in &mods {
|
|
for v in m.views.values() {
|
|
if let View::Form(form) = v {
|
|
for f in &form.fields {
|
|
let _ok = matches!(
|
|
f.kind,
|
|
FieldKind::Text
|
|
| FieldKind::Multiline
|
|
| FieldKind::Number
|
|
| FieldKind::Boolean
|
|
| FieldKind::Date
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn every_module_validates_clean() {
|
|
// validate() chequea que cada MenuItem.view exista en views.
|
|
// Un typo en cualquiera de los 6 módulos haría fallar este test.
|
|
let mods = load_modules_from_dir(examples_dir()).unwrap();
|
|
for m in &mods {
|
|
m.validate()
|
|
.unwrap_or_else(|e| panic!("module {} failed validate: {e}", m.id));
|
|
}
|
|
}
|