From 13408e79b940e9a33ca593ed30d1b20c54e01234 Mon Sep 17 00:00:00 2001
From: 魏曹先生 <1992414357@qq.com>
Date: Tue, 30 Jun 2026 18:05:05 +0800
Subject: feat(docs): add Chinese and English documentation for Mingling
tutorials
Add comprehensive documentation covering Declare a Dispatcher, Declare a
Chain, Rendering Results, Multi-Command Program, Argument Parsing with
Picker and Clap, Program Setup, Error Handling, Help Info, Resource
System, Exit Code Control, Hook System, Testing, Completion, Structural
Rendering, and Core Concepts
---
docs/pages/14-testing.md | 129 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 129 insertions(+)
create mode 100644 docs/pages/14-testing.md
(limited to 'docs/pages/14-testing.md')
diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md
new file mode 100644
index 0000000..789da96
--- /dev/null
+++ b/docs/pages/14-testing.md
@@ -0,0 +1,129 @@
+
Testing Your Program
+
+ Writing unit tests for Chain and Renderer
+
+
+A built-in benefit of the pipeline model is **testability**.
+
+A Chain is just a function that takes input and returns output; a Renderer is just a function that takes input and writes content — no global state magic, testing is straightforward.
+
+## Testing Renderer
+
+Renderer is the easiest to test — call the function, assert the result:
+
+```rust
+@@@pack!(ResultName = String);
+// Returns String instead of ()
+#[renderer]
+fn render_name(name: ResultName) -> String {
+ r_println!("Hello, {}!", *name);
+}
+
+#[test]
+fn test_render_name() {
+ let result = render_name(ResultName::new("Alice".to_string()));
+ assert_eq!(result, "Hello, Alice!\n");
+}
+```
+
+Note the Renderer's return type changed to `-> String` — `#[renderer]` auto-converts `RenderResult` to whatever return type you specify (default is `()`). By returning `String`, you can directly assert on the output content.
+
+## Testing Chain
+
+Testing a Chain is slightly more complex because its return value is `Next` (actually `impl Into>`). You'll need the assertion macros provided by the framework:
+
+```rust
+@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
+@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@pack!(ResultName = String);
+@@@pack!(ErrorNoName = ());
+@@@#[chain]
+@@@fn handle_hello(args: EntryHello) -> Next {
+@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ if name.is_empty() {
+@@@ ErrorNoName::default().to_render()
+@@@ } else {
+@@@ ResultName::new(name).to_render()
+@@@ }
+@@@}
+#[test]
+fn test_handle_hello_with_name() {
+ let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
+ // Asserts this is a render result (not continuing the chain)
+ assert_render_result!(chain_process);
+ // Asserts member_id is ResultName
+ assert_member_id!(chain_process, ResultName);
+ // Unpacks the inner value
+ let result_name = unpack_chain_process!(chain_process, ResultName);
+ assert_eq!(result_name.inner, "Alice");
+}
+```
+
+What the three test macros do:
+
+| Macro | Function |
+| ----------------------- | ----------------------------------------------------------------- |
+| `assert_render_result!` | Asserts Chain returned the render path (not continuing the chain) |
+| `assert_member_id!` | Asserts the return value's member ID is a certain type |
+| `unpack_chain_process!` | Unpacks the original type from ChainProcess |
+
+## Constructing Data with the entry! Macro
+
+If `extra_macros` is enabled, you can use `entry!` to quickly construct an Entry:
+
+```rust
+// Features: ["extra_macros"]
+
+@@@use mingling::{assert_member_id, unpack_chain_process};
+@@@use mingling::macros::entry;
+@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@pack!(ResultName = String);
+@@@#[chain]
+@@@fn handle_hello(args: EntryHello) -> Next {
+@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ ResultName::new(name).to_render()
+@@@}
+#[test]
+fn test_with_entry_macro() {
+ // entry! constructs an Entry from string literals
+ let entry = entry!("--name", "Alice");
+ let chain_process = handle_hello(entry).into();
+ let result_name = unpack_chain_process!(chain_process, ResultName);
+ assert_eq!(result_name.inner, "Alice");
+}
+```
+
+## Testing Resource Injection
+
+If a Chain uses resources, you need to provide resource instances in the test:
+
+```rust
+@@@use mingling::{assert_render_result, unpack_chain_process};
+@@@#[derive(Default, Clone)]
+@@@struct ResPrefix(String);
+@@@dispatcher!("hello", CMDHello => EntryHello);
+@@@pack!(ResultGreeting = String);
+@@@
+#[chain]
+fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
+ let name = args.inner.first().cloned().unwrap_or_default();
+ ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
+}
+
+#[test]
+fn test_handle_with_resource() {
+ // Resources need to be passed manually in tests
+ let result = handle_hello(
+ EntryHello::new(vec!["World".to_string()]),
+ &ResPrefix("Hello".to_string()),
+ );
+ let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
+ assert_eq!(greeting.inner, "Hello, World");
+}
+```
+
+The pipeline model makes testing simple: each Chain and Renderer is a relatively independent function — construct input, assert output.
+
+
+ Written by @Weicao-CatilGrass
+
--
cgit