1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Headless Alfred workflow simulator.
//!
//! This module provides tools for parsing, navigating, and testing Alfred workflows
//! without the Alfred UI. It includes:
//!
//! - [`WorkflowGraph`] — a parsed representation of an Alfred `info.plist`, modeling
//! objects (Script Filters, actions, utilities) and their connections.
//! - [`Simulator`] — an invocation harness that runs workflows in-process or via
//! subprocess and returns a [`Screen`].
//! - [`Screen`] / [`ScreenItem`] — parsed Script Filter output for inspection.
//! - [`ActionResult`] — the outcome of actioning an item (drill-in, open URL, etc.).
//! - Graph queries: reachability, keyword lookup, and navigation auditing.
//!
//! # Writing behavioural tests
//!
//! The primary use case is testing workflow navigation headlessly. Workflow authors
//! create a [`Simulator`] pointed at their workflow directory (which contains the
//! real `info.plist`), then call [`Simulator::run_in_process`] with their `Runnable`
//! to get a [`Screen`]. Each screen supports action routing through the graph:
//!
//! ```no_run
//! use alfrusco::simulator::{ActionResult, Simulator};
//! use alfrusco::{Item, Runnable, Workflow};
//!
//! struct MyWorkflow { query: String }
//! impl Runnable for MyWorkflow {
//! type Error = alfrusco::Error;
//! fn run(self, wf: &mut Workflow) -> Result<(), Self::Error> {
//! wf.append_item(Item::new("Fruits").arg("fruits").valid(true));
//! Ok(())
//! }
//! }
//!
//! // Point at your workflow directory (containing info.plist)
//! let sim = Simulator::for_workflow_dir("examples/menu_workflow").unwrap();
//!
//! // Run in-process — no compiled binary or deployment needed
//! let screen = sim.run_in_process(MyWorkflow { query: "".into() }).unwrap();
//! screen.assert_renders();
//!
//! // Verify action routing through the plist graph
//! let result = screen.action_first().unwrap();
//! result.assert_drills_in();
//! ```
//!
//! # Parsing and inspecting screens directly
//!
//! For unit-test assertions that don't need graph routing, parse JSON directly:
//!
//! ```
//! use alfrusco::simulator::Screen;
//!
//! let json = r#"{"items":[
//! {"title":"Apple","subtitle":"A fruit","arg":"apple","valid":true},
//! {"title":"Back","valid":false,"autocomplete":""}
//! ]}"#;
//!
//! let screen = Screen::from_json(json).unwrap();
//! assert_eq!(screen.len(), 2);
//! assert_eq!(screen.items()[0].title(), "Apple");
//! assert_eq!(screen.items()[0].arg(), Some("apple"));
//! assert!(screen.items()[0].is_valid());
//! assert!(!screen.items()[1].is_valid());
//! ```
//!
//! # Graph auditing
//!
//! Use [`WorkflowGraph`] to statically audit your workflow's navigation:
//!
//! ```no_run
//! use alfrusco::simulator::WorkflowGraph;
//!
//! let graph = WorkflowGraph::from_plist_file("examples/menu_workflow/info.plist").unwrap();
//!
//! // Check that a keyword reaches its action chain
//! assert!(graph.reaches_script_filter("SF-MAIN-001"));
//!
//! // Audit reports navigation defects (dead-ends, dangling connections)
//! let diagnostics = graph.audit_navigation(&["menu"]);
//! assert!(diagnostics.is_empty(), "unexpected defects: {diagnostics:?}");
//! ```
pub use ActionResult;
pub use ;
pub use ;
pub use ;