···224224225225 nodes.push(Node::Heading {
226226 level,
227227- // Here, we are `get`ing the `&str` starting at the `level`-th
228228- // index, then trimming the start. These operations
229229- // effectively off the line identifier.
230230- text: line.get(level..).unwrap_or("").trim_start().to_string(),
227227+ // Here, the text after the heading markers is safely extracted.
228228+ // `chars().skip()` is used to safely handle UTF-8 boundaries.
229229+ text: line
230230+ .chars()
231231+ .skip(level)
232232+ .collect::<String>()
233233+ .trim_start()
234234+ .to_string(),
231235 });
232236233237 break;
+40-1
tests/ast.rs
···9595 assert_eq!(to, "");
9696 assert_eq!(text, &None);
9797 } else {
9898- panic!("Expected Link node");
9898+ panic!("Expected link node");
9999+ }
100100+ }
101101+102102+ #[test]
103103+ fn build_heading_with_unicode_and_edge_cases() {
104104+ // Unicode characters
105105+ let ast = Ast::from_string("# Hello, 世界!");
106106+107107+ assert_eq!(ast.inner().len(), 1);
108108+109109+ if let Node::Heading { level, text } = ast.inner().first().unwrap() {
110110+ assert_eq!(level, &1);
111111+ assert_eq!(text, "Hello, 世界!");
112112+ } else {
113113+ panic!("Expected heading node");
114114+ }
115115+116116+ // Only hashes
117117+ let ast = Ast::from_string("###");
118118+119119+ assert_eq!(ast.inner().len(), 1);
120120+121121+ if let Node::Heading { level, text } = ast.inner().first().unwrap() {
122122+ assert_eq!(level, &3);
123123+ assert_eq!(text, "");
124124+ } else {
125125+ panic!("Expected heading node");
126126+ }
127127+128128+ // Many hashes
129129+ let ast = Ast::from_string("########## Very Deep Heading");
130130+131131+ assert_eq!(ast.inner().len(), 1);
132132+133133+ if let Node::Heading { level, text } = ast.inner().first().unwrap() {
134134+ assert_eq!(level, &10);
135135+ assert_eq!(text, "Very Deep Heading");
136136+ } else {
137137+ panic!("Expected heading node");
99138 }
100139 }
101140}