Skip to main content

yaml_edit/nodes/
sequence.rs

1use super::{Lang, SyntaxNode};
2use crate::as_yaml::{AsYaml, YamlKind};
3use crate::lex::SyntaxKind;
4use crate::yaml::ValueNode;
5use rowan::ast::AstNode;
6use rowan::GreenNodeBuilder;
7
8ast_node!(Sequence, SEQUENCE, "A YAML sequence (list)");
9
10impl Sequence {
11    /// Iterate over items in this sequence as raw syntax nodes.
12    ///
13    /// For most use cases prefer [`values`](Self::values) which returns
14    /// [`YamlNode`](crate::as_yaml::YamlNode)s.
15    pub(crate) fn items(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
16        self.0.children().filter_map(|child| {
17            if child.kind() == SyntaxKind::SEQUENCE_ENTRY {
18                // Look for the actual item within the SEQUENCE_ENTRY
19                // Skip DASH and WHITESPACE tokens, find the actual value node
20                child.children().find(|n| {
21                    matches!(
22                        n.kind(),
23                        SyntaxKind::SCALAR
24                            | SyntaxKind::MAPPING
25                            | SyntaxKind::SEQUENCE
26                            | SyntaxKind::ALIAS
27                            | SyntaxKind::TAGGED_NODE
28                    )
29                })
30            } else {
31                None
32            }
33        })
34    }
35
36    /// Iterate over items in this sequence as [`YamlNode`](crate::as_yaml::YamlNode)s.
37    ///
38    /// Items that cannot be wrapped as a `YamlNode` are silently skipped.
39    pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
40        self.items()
41            .filter_map(crate::as_yaml::YamlNode::from_syntax)
42    }
43
44    /// Returns the number of items in this sequence.
45    pub fn len(&self) -> usize {
46        self.items().count()
47    }
48
49    /// Returns `true` if this sequence contains no items.
50    pub fn is_empty(&self) -> bool {
51        self.items().next().is_none()
52    }
53
54    /// Get the item at `index` as a [`YamlNode`](crate::as_yaml::YamlNode).
55    ///
56    /// Returns `None` if `index` is out of bounds.
57    pub fn get(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
58        self.items()
59            .nth(index)
60            .and_then(crate::as_yaml::YamlNode::from_syntax)
61    }
62
63    /// Get the first item in this sequence, or `None` if empty.
64    pub fn first(&self) -> Option<crate::as_yaml::YamlNode> {
65        self.get(0)
66    }
67
68    /// Get the last item in this sequence, or `None` if empty.
69    pub fn last(&self) -> Option<crate::as_yaml::YamlNode> {
70        let len = self.len();
71        if len == 0 {
72            None
73        } else {
74            self.get(len - 1)
75        }
76    }
77}
78
79impl Sequence {
80    /// Detect the indentation used by entries in this sequence.
81    ///
82    /// First looks for a top-level INDENT token, then falls back to looking
83    /// for WHITESPACE immediately before DASH inside SEQUENCE_ENTRY nodes.
84    /// Returns `"  "` (two spaces) if no indentation can be detected.
85    fn detect_indentation(&self) -> String {
86        // First try top-level INDENT tokens
87        if let Some(ind) = self.0.children_with_tokens().find_map(|child| {
88            child
89                .into_token()
90                .filter(|t| t.kind() == SyntaxKind::INDENT)
91                .map(|t| t.text().to_string())
92        }) {
93            return ind;
94        }
95
96        // Fall back: look for WHITESPACE before DASH inside entry nodes
97        self.0
98            .children()
99            .filter(|c| c.kind() == SyntaxKind::SEQUENCE_ENTRY)
100            .find_map(|entry| {
101                let tokens: Vec<_> = entry.children_with_tokens().collect();
102                tokens.windows(2).find_map(|pair| {
103                    let ws = pair[0].as_token()?;
104                    let dash = pair[1].as_token()?;
105                    if ws.kind() == SyntaxKind::WHITESPACE && dash.kind() == SyntaxKind::DASH {
106                        Some(ws.text().to_string())
107                    } else {
108                        None
109                    }
110                })
111            })
112            .unwrap_or_else(|| "  ".to_string())
113    }
114
115    /// Add an item to the end of the sequence.
116    ///
117    /// Mutates in place despite `&self` (see crate docs on interior mutability).
118    pub fn push(&self, value: impl crate::AsYaml) {
119        let indentation = self.detect_indentation();
120
121        // Build the INDENT token (separate from the SEQUENCE_ENTRY)
122        let mut indent_builder = GreenNodeBuilder::new();
123        indent_builder.start_node(SyntaxKind::ROOT.into());
124        indent_builder.token(SyntaxKind::INDENT.into(), &indentation);
125        indent_builder.finish_node();
126        let indent_node = SyntaxNode::new_root_mut(indent_builder.finish());
127        let indent_token = indent_node
128            .first_token()
129            .expect("builder always emits an INDENT token");
130
131        // Collect children and analyze the sequence structure
132        let children: Vec<_> = self.0.children_with_tokens().collect();
133
134        // Find the last SEQUENCE_ENTRY and check if it has a trailing newline
135        let mut last_entry_has_newline = true; // Default to true for empty sequences
136        let mut last_entry_index = None;
137
138        for (i, child) in children.iter().enumerate().rev() {
139            if let Some(node) = child.as_node() {
140                if node.kind() == SyntaxKind::SEQUENCE_ENTRY {
141                    last_entry_has_newline = node
142                        .last_token()
143                        .map(|t| t.kind() == SyntaxKind::NEWLINE)
144                        .unwrap_or(false);
145                    last_entry_index = Some(i);
146                    break;
147                }
148            }
149        }
150
151        // Find the insert position: after the last SEQUENCE_ENTRY and any immediately following
152        // INDENT tokens, but BEFORE any trailing standalone NEWLINE tokens (which represent
153        // blank lines that should stay between mapping entries, not inside the sequence)
154        let mut insert_pos = children.len();
155        if let Some(last_idx) = last_entry_index {
156            // Start from after the last SEQUENCE_ENTRY
157            insert_pos = last_idx + 1;
158
159            // Skip any INDENT tokens immediately after
160            while insert_pos < children.len() {
161                if let Some(token) = children[insert_pos].as_token() {
162                    if token.kind() == SyntaxKind::INDENT {
163                        insert_pos += 1;
164                    } else {
165                        break;
166                    }
167                } else {
168                    break;
169                }
170            }
171            // Now insert_pos is right before any trailing standalone NEWLINE tokens
172        }
173
174        // Build the SEQUENCE_ENTRY node using AsYaml trait
175        let mut builder = GreenNodeBuilder::new();
176        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
177        builder.token(SyntaxKind::DASH.into(), "-");
178        builder.token(SyntaxKind::WHITESPACE.into(), " ");
179
180        // Build the value content directly using AsYaml
181        let value_ends_with_newline = value.build_content(&mut builder, 0, false);
182
183        // Add trailing newline only if the value doesn't already end with one
184        // and if the last entry had one (preserves document style)
185        if last_entry_has_newline && !value_ends_with_newline {
186            builder.token(SyntaxKind::NEWLINE.into(), "\n");
187        }
188        builder.finish_node(); // SEQUENCE_ENTRY
189        let new_entry = SyntaxNode::new_root_mut(builder.finish());
190
191        // Ensure the previous last entry has a trailing newline (it won't be last anymore)
192        if let Some(last_idx) = last_entry_index {
193            if let Some(node) = children[last_idx].as_node() {
194                if !node
195                    .last_token()
196                    .map(|t| t.kind() == SyntaxKind::NEWLINE)
197                    .unwrap_or(false)
198                {
199                    let entry_children_count = node.children_with_tokens().count();
200                    let mut nl_builder = GreenNodeBuilder::new();
201                    nl_builder.start_node(SyntaxKind::ROOT.into());
202                    nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
203                    nl_builder.finish_node();
204                    let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
205                    if let Some(token) = nl_node.first_token() {
206                        node.splice_children(
207                            entry_children_count..entry_children_count,
208                            vec![token.into()],
209                        );
210                    }
211                }
212            }
213        }
214
215        // Insert the indent token and new entry before any trailing blank newlines
216        self.0.splice_children(
217            insert_pos..insert_pos,
218            vec![indent_token.into(), new_entry.into()],
219        );
220    }
221
222    /// Insert an item at a specific position.
223    ///
224    /// If `index` is out of bounds, the item is appended at the end.
225    /// This method always succeeds; it never returns an error.
226    ///
227    /// Mutates in place despite `&self` (see crate docs on interior mutability).
228    pub fn insert(&self, index: usize, value: impl crate::AsYaml) {
229        let indentation = self.detect_indentation();
230
231        // Build a newline token
232        let mut newline_builder = GreenNodeBuilder::new();
233        newline_builder.start_node(SyntaxKind::ROOT.into());
234        newline_builder.token(SyntaxKind::NEWLINE.into(), "\n");
235        newline_builder.finish_node();
236        let newline_node = SyntaxNode::new_root_mut(newline_builder.finish());
237        let newline_token = newline_node
238            .first_token()
239            .expect("builder always emits a NEWLINE token");
240
241        // Build the SEQUENCE_ENTRY node using AsYaml
242        let mut builder = GreenNodeBuilder::new();
243        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
244        builder.token(SyntaxKind::WHITESPACE.into(), &indentation);
245        builder.token(SyntaxKind::DASH.into(), "-");
246        builder.token(SyntaxKind::WHITESPACE.into(), " ");
247
248        // Build the value content directly using AsYaml
249        value.build_content(&mut builder, 0, false);
250
251        builder.finish_node(); // SEQUENCE_ENTRY
252        let new_entry = SyntaxNode::new_root_mut(builder.finish());
253
254        // Find the position to insert
255        let children: Vec<_> = self.0.children_with_tokens().collect();
256        let mut item_count = 0;
257        let mut insert_pos = children.len();
258
259        for (i, child) in children.iter().enumerate() {
260            if let Some(node) = child.as_node() {
261                if node.kind() == SyntaxKind::SEQUENCE_ENTRY {
262                    if item_count == index {
263                        insert_pos = i;
264                        break;
265                    }
266                    item_count += 1;
267                }
268            }
269        }
270
271        // Insert newline and new entry at the position
272        self.0.splice_children(
273            insert_pos..insert_pos,
274            vec![newline_token.into(), new_entry.into()],
275        );
276    }
277
278    /// Replace the item at `index` with a new value.
279    ///
280    /// Returns `true` if the index was in bounds and the item was replaced,
281    /// `false` if `index >= len()`.
282    ///
283    /// Mutates in place despite `&self` (see crate docs on interior mutability).
284    pub fn set(&self, index: usize, value: impl crate::AsYaml) -> bool {
285        let children: Vec<_> = self.0.children_with_tokens().collect();
286        let mut item_count = 0;
287
288        for (i, child) in children.iter().enumerate() {
289            if let Some(node) = child.as_node() {
290                if node.kind() == SyntaxKind::SEQUENCE_ENTRY {
291                    if item_count == index {
292                        // Build a new SEQUENCE_ENTRY with the new value using AsYaml
293                        let entry_children: Vec<_> = node.children_with_tokens().collect();
294                        let mut builder = GreenNodeBuilder::new();
295                        builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
296
297                        let mut value_inserted = false;
298                        let mut trailing_text: Option<String> = None;
299
300                        for entry_child in entry_children {
301                            match &entry_child {
302                                rowan::NodeOrToken::Node(n)
303                                    if matches!(
304                                        n.kind(),
305                                        SyntaxKind::SCALAR
306                                            | SyntaxKind::MAPPING
307                                            | SyntaxKind::SEQUENCE
308                                            | SyntaxKind::TAGGED_NODE
309                                    ) =>
310                                {
311                                    // Extract trailing whitespace from the old value node.
312                                    // Multi-line values (e.g. nested mappings) contain a
313                                    // trailing NEWLINE+INDENT that must be preserved.
314                                    let text = n.text().to_string();
315                                    if let Some(last_newline_pos) = text.rfind('\n') {
316                                        trailing_text = Some(text[last_newline_pos..].to_string());
317                                    }
318
319                                    // Replace the value node with the new value built from AsYaml
320                                    if !value_inserted {
321                                        value.build_content(&mut builder, 0, false);
322                                        value_inserted = true;
323                                    }
324                                }
325                                rowan::NodeOrToken::Node(n) => {
326                                    // Copy other nodes as-is (like VALUE wrappers, etc.)
327                                    crate::yaml::copy_node_to_builder(&mut builder, n);
328                                }
329                                rowan::NodeOrToken::Token(t) => {
330                                    // Copy tokens as-is
331                                    builder.token(t.kind().into(), t.text());
332                                }
333                            }
334                        }
335
336                        // Restore trailing whitespace extracted from the old value
337                        if let Some(trailing) = trailing_text {
338                            if let Some(indent_part) = trailing.strip_prefix('\n') {
339                                builder.token(SyntaxKind::NEWLINE.into(), "\n");
340                                if !indent_part.is_empty() {
341                                    builder.token(SyntaxKind::INDENT.into(), indent_part);
342                                }
343                            }
344                        }
345
346                        builder.finish_node();
347                        let new_entry = SyntaxNode::new_root_mut(builder.finish());
348
349                        // Replace the old SEQUENCE_ENTRY with the new one
350                        self.0.splice_children(i..i + 1, vec![new_entry.into()]);
351                        return true;
352                    }
353                    item_count += 1;
354                }
355            }
356        }
357        false
358    }
359
360    /// Remove the item at `index`, returning its value.
361    ///
362    /// Returns `Some(value)` if the index was in bounds, `None` otherwise.
363    ///
364    /// Mutates in place despite `&self` (see crate docs on interior mutability).
365    pub fn remove(&self, index: usize) -> Option<crate::as_yaml::YamlNode> {
366        // Capture the value before removing so we can return it
367        let removed_value = self.get(index);
368
369        // Use children_with_tokens() since splice_children() expects those indices
370        let children: Vec<_> = self.0.children_with_tokens().collect();
371
372        // Find the SEQUENCE_ENTRY at the given index
373        let mut item_count = 0;
374        for (i, child) in children.iter().enumerate() {
375            if let Some(node) = child.as_node() {
376                if node.kind() == SyntaxKind::SEQUENCE_ENTRY {
377                    if item_count == index {
378                        // Check if this is the last SEQUENCE_ENTRY
379                        let is_last = !children.iter().skip(i + 1).any(|c| {
380                            c.as_node()
381                                .is_some_and(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
382                        });
383
384                        // Remove the entry
385                        self.0.splice_children(i..(i + 1), vec![]);
386
387                        if !self.is_flow_style() && is_last && i > 0 {
388                            // Removed the last entry - remove trailing newline/indent from new last entry
389                            // Find the previous SEQUENCE_ENTRY
390                            if let Some(prev_entry_node) =
391                                children[..i].iter().rev().find_map(|c| {
392                                    c.as_node()
393                                        .filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
394                                })
395                            {
396                                // Remove trailing NEWLINE and INDENT tokens
397                                let entry_children: Vec<_> =
398                                    prev_entry_node.children_with_tokens().collect();
399                                let mut remove_count = 0;
400
401                                // Count trailing NEWLINE, INDENT, and WHITESPACE tokens from the end
402                                for child in entry_children.iter().rev() {
403                                    if let Some(token) = child.as_token() {
404                                        if matches!(
405                                            token.kind(),
406                                            SyntaxKind::NEWLINE
407                                                | SyntaxKind::INDENT
408                                                | SyntaxKind::WHITESPACE
409                                        ) {
410                                            remove_count += 1;
411                                        } else {
412                                            break;
413                                        }
414                                    } else {
415                                        break;
416                                    }
417                                }
418
419                                if remove_count > 0 {
420                                    let total = entry_children.len();
421                                    prev_entry_node
422                                        .splice_children((total - remove_count)..total, vec![]);
423                                }
424                            }
425                        }
426                        return removed_value;
427                    }
428                    item_count += 1;
429                }
430            }
431        }
432        None
433    }
434
435    /// Check if this sequence is in flow style [item1, item2]
436    pub fn is_flow_style(&self) -> bool {
437        self.0.children_with_tokens().any(|child| {
438            child
439                .as_token()
440                .is_some_and(|t| t.kind() == SyntaxKind::LEFT_BRACKET)
441        })
442    }
443
444    /// Get the raw syntax node for a specific index (for advanced use).
445    ///
446    /// Returns the raw CST node without decoding it to a value.
447    /// For most use cases prefer [`get`](Self::get), which returns a [`YamlNode`](crate::YamlNode).
448    #[allow(dead_code)] // Used in tests
449    pub(crate) fn get_node(&self, index: usize) -> Option<SyntaxNode> {
450        self.items().nth(index)
451    }
452
453    /// Remove and return the last item in this sequence.
454    ///
455    /// Returns `None` if the sequence is empty.
456    ///
457    /// Mutates in place despite `&self` (see crate docs on interior mutability).
458    pub fn pop(&self) -> Option<crate::as_yaml::YamlNode> {
459        let len = self.len();
460        if len == 0 {
461            return None;
462        }
463        let removed = self.remove(len - 1);
464
465        debug_assert_eq!(
466            self.len(),
467            len - 1,
468            "pop() invariant: remove() did not reduce length"
469        );
470
471        removed
472    }
473
474    /// Remove all items from this sequence.
475    ///
476    /// Mutates in place despite `&self` (see crate docs on interior mutability).
477    pub fn clear(&self) {
478        // Remove items from the beginning to avoid recalculating indices
479        // Use a safety counter to prevent infinite loops
480        let initial_len = self.len();
481        for _ in 0..initial_len {
482            let current_len = self.len();
483            if current_len == 0 {
484                break;
485            }
486            // Always remove the first item
487            let removed = self.remove(0);
488            debug_assert!(
489                removed.is_some(),
490                "clear() invariant: remove(0) returned None"
491            );
492            debug_assert_eq!(
493                self.len(),
494                current_len - 1,
495                "clear() invariant: remove(0) did not reduce length"
496            );
497        }
498    }
499
500    /// Get the byte offset range of this sequence in the source text.
501    ///
502    /// Returns the start and end byte offsets as a `TextPosition`.
503    pub fn byte_range(&self) -> crate::TextPosition {
504        self.0.text_range().into()
505    }
506
507    /// Get the line and column where this sequence starts.
508    ///
509    /// Requires the original source text to calculate line/column from byte offsets.
510    /// Line and column numbers are 1-indexed.
511    ///
512    /// # Arguments
513    ///
514    /// * `source_text` - The original YAML source text
515    pub fn start_position(&self, source_text: &str) -> crate::LineColumn {
516        let range = self.byte_range();
517        crate::byte_offset_to_line_column(source_text, range.start as usize)
518    }
519
520    /// Get the line and column where this sequence ends.
521    ///
522    /// Requires the original source text to calculate line/column from byte offsets.
523    /// Line and column numbers are 1-indexed.
524    ///
525    /// # Arguments
526    ///
527    /// * `source_text` - The original YAML source text
528    pub fn end_position(&self, source_text: &str) -> crate::LineColumn {
529        let range = self.byte_range();
530        crate::byte_offset_to_line_column(source_text, range.end as usize)
531    }
532}
533
534// Iterator trait implementations for Sequence
535
536impl<'a> IntoIterator for &'a Sequence {
537    type Item = crate::as_yaml::YamlNode;
538    type IntoIter = Box<dyn Iterator<Item = crate::as_yaml::YamlNode> + 'a>;
539
540    fn into_iter(self) -> Self::IntoIter {
541        Box::new(self.values())
542    }
543}
544
545impl AsYaml for Sequence {
546    fn as_node(&self) -> Option<&SyntaxNode> {
547        Some(&self.0)
548    }
549
550    fn kind(&self) -> YamlKind {
551        YamlKind::Sequence
552    }
553
554    fn build_content(
555        &self,
556        builder: &mut rowan::GreenNodeBuilder,
557        indent: usize,
558        _flow_context: bool,
559    ) -> bool {
560        builder.start_node(SyntaxKind::SEQUENCE.into());
561        crate::as_yaml::copy_node_content_with_indent(builder, &self.0, indent);
562        builder.finish_node();
563        self.0
564            .last_token()
565            .map(|t| t.kind() == SyntaxKind::NEWLINE)
566            .unwrap_or(false)
567    }
568
569    fn is_inline(&self) -> bool {
570        ValueNode::is_inline(self)
571    }
572}
573#[cfg(test)]
574mod tests {
575    use crate::yaml::YamlFile;
576    use std::str::FromStr;
577
578    #[test]
579    fn test_sequence_items_tagged_node() {
580        // Tagged scalars inside sequences were previously skipped by items() because
581        // TAGGED_NODE was not listed in the kind filter.
582        let yaml = "- !custom foo\n- !custom bar\n- plain\n";
583        let parsed = YamlFile::from_str(yaml).unwrap();
584
585        let doc = parsed.document().unwrap();
586        let seq = doc.as_sequence().unwrap();
587        assert_eq!(
588            seq.items().count(),
589            3,
590            "Tagged scalars should be included in items()"
591        );
592        // values() should also return tagged scalars (cast as Scalar YamlValues)
593        assert_eq!(
594            seq.values().count(),
595            3,
596            "Tagged scalars should be included in values()"
597        );
598    }
599
600    #[test]
601    fn test_sequence_set_tagged_node() {
602        // Sequence::set() was missing TAGGED_NODE from its kind filter, so
603        // replacing a tagged-scalar item would leave the original tag+value in place
604        // and insert the new value alongside it.
605        let yaml = "- !custom foo\n- bar\n";
606        let parsed = YamlFile::from_str(yaml).unwrap();
607        let doc = parsed.document().unwrap();
608        let seq = doc.as_sequence().unwrap();
609
610        seq.set(0, "replaced");
611
612        let values: Vec<_> = seq.values().collect();
613        assert_eq!(values.len(), 2);
614        assert_eq!(
615            values[0].as_scalar().map(|s| s.as_string()),
616            Some("replaced".to_string())
617        );
618        assert_eq!(
619            values[1].as_scalar().map(|s| s.as_string()),
620            Some("bar".to_string())
621        );
622    }
623
624    #[test]
625    fn test_sequence_operations() {
626        let yaml = "- item1\n- item2";
627        let parsed = YamlFile::from_str(yaml).unwrap();
628
629        let doc = parsed.document().expect("expected a document");
630        let seq = doc.as_sequence().expect("expected a sequence");
631
632        // Test push
633        seq.push("item3");
634        let values: Vec<_> = seq.values().collect();
635        assert_eq!(values.len(), 3);
636        assert_eq!(
637            values[2].as_scalar().map(|s| s.as_string()),
638            Some("item3".to_string())
639        );
640
641        // Test insert
642        seq.insert(0, "item0");
643        let values: Vec<_> = seq.values().collect();
644        assert_eq!(values.len(), 4);
645        assert_eq!(
646            values[0].as_scalar().map(|s| s.as_string()),
647            Some("item0".to_string())
648        );
649    }
650
651    // Iterator tests
652
653    #[test]
654    fn test_sequence_into_iterator() {
655        use crate::Document;
656        let text = "items:\n  - apple\n  - banana\n  - cherry";
657        let doc = Document::from_str(text).unwrap();
658        let mapping = doc.as_mapping().unwrap();
659        let sequence = mapping.get_sequence("items").unwrap();
660
661        // Test that we can use for loops directly
662        let mut items = Vec::new();
663        for value in &sequence {
664            if let Some(scalar) = value.as_scalar() {
665                items.push(scalar.to_string());
666            }
667        }
668
669        assert_eq!(items.len(), 3);
670        assert_eq!(items[0], "apple");
671        assert_eq!(items[1], "banana");
672        assert_eq!(items[2], "cherry");
673    }
674
675    #[test]
676    fn test_sequence_into_iterator_count() {
677        use crate::Document;
678        let text = "[1, 2, 3, 4, 5]";
679        let doc = Document::from_str(text).unwrap();
680        let sequence = doc.as_sequence().unwrap();
681
682        let count = (&sequence).into_iter().count();
683        assert_eq!(count, 5);
684    }
685
686    #[test]
687    fn test_sequence_iterator_map() {
688        use crate::Document;
689        let text = "numbers: [1, 2, 3]";
690        let doc = Document::from_str(text).unwrap();
691        let mapping = doc.as_mapping().unwrap();
692        let sequence = mapping.get_sequence("numbers").unwrap();
693
694        // Map to strings
695        let strings: Vec<_> = (&sequence)
696            .into_iter()
697            .filter_map(|v| v.as_scalar().map(|s| s.to_string()))
698            .collect();
699
700        assert_eq!(strings, vec!["1", "2", "3"]);
701    }
702
703    #[test]
704    fn test_empty_sequence_iterator() {
705        use crate::Document;
706        let text = "items: []";
707        let doc = Document::from_str(text).unwrap();
708        let mapping = doc.as_mapping().unwrap();
709        let sequence = mapping.get_sequence("items").unwrap();
710
711        let count = (&sequence).into_iter().count();
712        assert_eq!(count, 0);
713    }
714
715    // Tests from sequence_operations_test.rs
716
717    #[test]
718    fn test_sequence_push_single() {
719        use crate::Document;
720        let original = r#"team:
721  - Alice
722  - Bob"#;
723
724        let doc = Document::from_str(original).unwrap();
725        let mapping = doc.as_mapping().unwrap();
726        let team = mapping.get_sequence("team").unwrap();
727        team.push("Charlie");
728
729        let expected = r#"team:
730  - Alice
731  - Bob
732  - Charlie"#;
733        assert_eq!(doc.to_string(), expected);
734    }
735
736    #[test]
737    fn test_sequence_push_multiple() {
738        use crate::Document;
739        let original = r#"team:
740  - Alice
741  - Bob"#;
742
743        let doc = Document::from_str(original).unwrap();
744        let mapping = doc.as_mapping().unwrap();
745        let team = mapping.get_sequence("team").unwrap();
746        team.push("Charlie");
747        team.push("Diana");
748
749        let expected = r#"team:
750  - Alice
751  - Bob
752  - Charlie
753  - Diana"#;
754        assert_eq!(doc.to_string(), expected);
755    }
756
757    #[test]
758    fn test_sequence_set_item() {
759        use crate::Document;
760        let original = r#"team:
761  - Alice
762  - Bob
763  - Charlie"#;
764
765        let doc = Document::from_str(original).unwrap();
766        let mapping = doc.as_mapping().unwrap();
767        let team = mapping.get_sequence("team").unwrap();
768        team.set(1, "Robert");
769
770        let expected = r#"team:
771  - Alice
772  - Robert
773  - Charlie"#;
774        assert_eq!(doc.to_string(), expected);
775    }
776
777    #[test]
778    fn test_multiple_sequences() {
779        use crate::Document;
780        let original = r#"team:
781  - Alice
782  - Bob
783
784scores:
785  - 95
786  - 87"#;
787
788        let doc = Document::from_str(original).unwrap();
789        let mapping = doc.as_mapping().unwrap();
790        let team = mapping.get_sequence("team").unwrap();
791        team.push("Charlie");
792        let scores = mapping.get_sequence("scores").unwrap();
793        scores.push(92);
794        scores.set(0, 100);
795
796        let expected = r#"team:
797  - Alice
798  - Bob
799  - Charlie
800
801scores:
802  - 100
803  - 87
804  - 92"#;
805        assert_eq!(doc.to_string(), expected);
806    }
807
808    #[test]
809    fn test_nested_structure_with_sequences() {
810        use crate::Document;
811        let original = r#"config:
812  enabled: true
813  retries: 3
814  servers:
815    - host1
816    - host2"#;
817
818        let doc = Document::from_str(original).unwrap();
819        let mapping = doc.as_mapping().unwrap();
820        let config = mapping.get_mapping("config").unwrap();
821        config.set("enabled", false);
822        config.set("retries", 5);
823
824        let servers = config.get_sequence("servers").unwrap();
825        servers.push("host3");
826        servers.set(0, "primary-host");
827
828        let expected = r#"config:
829  enabled: false
830  retries: 5
831  servers:
832    - primary-host
833    - host2
834    - host3"#;
835        assert_eq!(doc.to_string(), expected);
836    }
837
838    #[test]
839    fn test_sequence_len_and_is_empty() {
840        use crate::Document;
841        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
842        let mapping = doc.as_mapping().unwrap();
843        let seq = mapping.get_sequence("items").unwrap();
844
845        assert_eq!(seq.len(), 3);
846        assert!(!seq.is_empty());
847
848        let empty_doc = Document::from_str("items: []").unwrap();
849        let empty_mapping = empty_doc.as_mapping().unwrap();
850        let empty_seq = empty_mapping.get_sequence("items").unwrap();
851
852        assert_eq!(empty_seq.len(), 0);
853        assert!(empty_seq.is_empty());
854    }
855
856    #[test]
857    fn test_sequence_get() {
858        use crate::Document;
859        let doc = Document::from_str("items:\n  - first\n  - second\n  - third").unwrap();
860        let mapping = doc.as_mapping().unwrap();
861        let seq = mapping.get_sequence("items").unwrap();
862
863        assert_eq!(seq.get(0).unwrap().to_string(), "first");
864        assert_eq!(seq.get(1).unwrap().to_string(), "second");
865        assert_eq!(seq.get(2).unwrap().to_string(), "third");
866        assert!(seq.get(3).is_none());
867    }
868
869    #[test]
870    fn test_sequence_first_and_last() {
871        use crate::Document;
872        let doc = Document::from_str("items:\n  - first\n  - middle\n  - last").unwrap();
873        let mapping = doc.as_mapping().unwrap();
874        let seq = mapping.get_sequence("items").unwrap();
875
876        assert_eq!(seq.first().unwrap().to_string(), "first");
877        assert_eq!(seq.last().unwrap().to_string(), "last");
878
879        let empty_doc = Document::from_str("items: []").unwrap();
880        let empty_mapping = empty_doc.as_mapping().unwrap();
881        let empty_seq = empty_mapping.get_sequence("items").unwrap();
882
883        assert!(empty_seq.first().is_none());
884        assert!(empty_seq.last().is_none());
885    }
886
887    #[test]
888    fn test_sequence_values_iterator() {
889        use crate::Document;
890        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
891        let mapping = doc.as_mapping().unwrap();
892        let seq = mapping.get_sequence("items").unwrap();
893
894        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
895        assert_eq!(values, vec!["a", "b", "c"]);
896    }
897
898    #[test]
899    fn test_sequence_pop() {
900        use crate::Document;
901        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
902        let mapping = doc.as_mapping().unwrap();
903        let seq = mapping.get_sequence("items").unwrap();
904
905        assert_eq!(seq.len(), 3);
906        let popped = seq.pop().unwrap();
907        assert_eq!(popped.to_string(), "c");
908        assert_eq!(seq.len(), 2);
909
910        let popped = seq.pop().unwrap();
911        assert_eq!(popped.to_string(), "b");
912        assert_eq!(seq.len(), 1);
913
914        let expected = "items:\n  - a";
915        assert_eq!(doc.to_string().trim_end(), expected);
916
917        let popped = seq.pop().unwrap();
918        assert_eq!(popped.to_string(), "a");
919        assert_eq!(seq.len(), 0);
920        assert!(seq.pop().is_none());
921    }
922
923    #[test]
924    fn test_sequence_clear() {
925        use crate::Document;
926        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
927        let mapping = doc.as_mapping().unwrap();
928        let seq = mapping.get_sequence("items").unwrap();
929
930        assert_eq!(seq.len(), 3);
931        seq.clear();
932        assert_eq!(seq.len(), 0);
933        assert!(seq.is_empty());
934    }
935
936    #[test]
937    fn test_sequence_get_with_nested_values() {
938        use crate::Document;
939        let doc = Document::from_str(
940            r#"items:
941  - simple
942  - {key: value}
943  - [nested, list]"#,
944        )
945        .unwrap();
946        let mapping = doc.as_mapping().unwrap();
947        let seq = mapping.get_sequence("items").unwrap();
948
949        assert_eq!(seq.len(), 3);
950        assert!(seq.get(0).unwrap().is_scalar());
951        assert!(seq.get(1).unwrap().is_mapping());
952        assert!(seq.get(2).unwrap().is_sequence());
953    }
954
955    #[test]
956    fn test_flow_sequence_len_and_is_empty() {
957        use crate::Document;
958        let doc = Document::from_str("items: [a, b, c]").unwrap();
959        let mapping = doc.as_mapping().unwrap();
960        let seq = mapping.get_sequence("items").unwrap();
961
962        assert_eq!(seq.len(), 3);
963        assert!(!seq.is_empty());
964
965        let empty_doc = Document::from_str("items: []").unwrap();
966        let empty_mapping = empty_doc.as_mapping().unwrap();
967        let empty_seq = empty_mapping.get_sequence("items").unwrap();
968
969        assert_eq!(empty_seq.len(), 0);
970        assert!(empty_seq.is_empty());
971    }
972
973    #[test]
974    fn test_flow_sequence_get() {
975        use crate::Document;
976        let doc = Document::from_str("items: [first, second, third]").unwrap();
977        let mapping = doc.as_mapping().unwrap();
978        let seq = mapping.get_sequence("items").unwrap();
979
980        assert_eq!(seq.get(0).unwrap().to_string(), "first");
981        assert_eq!(seq.get(1).unwrap().to_string(), "second");
982        assert_eq!(seq.get(2).unwrap().to_string(), "third");
983        assert!(seq.get(3).is_none());
984    }
985
986    #[test]
987    fn test_flow_sequence_first_and_last() {
988        use crate::Document;
989        let doc = Document::from_str("items: [first, middle, last]").unwrap();
990        let mapping = doc.as_mapping().unwrap();
991        let seq = mapping.get_sequence("items").unwrap();
992
993        assert_eq!(seq.first().unwrap().to_string(), "first");
994        assert_eq!(seq.last().unwrap().to_string(), "last");
995    }
996
997    #[test]
998    fn test_flow_sequence_values_iterator() {
999        use crate::Document;
1000        let doc = Document::from_str("items: [a, b, c]").unwrap();
1001        let mapping = doc.as_mapping().unwrap();
1002        let seq = mapping.get_sequence("items").unwrap();
1003
1004        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1005        assert_eq!(values, vec!["a", "b", "c"]);
1006    }
1007
1008    #[test]
1009    fn test_flow_sequence_remove_middle() {
1010        use crate::Document;
1011        let doc = Document::from_str("items: [a, b, c]").unwrap();
1012        let mapping = doc.as_mapping().unwrap();
1013        let seq = mapping.get_sequence("items").unwrap();
1014
1015        assert_eq!(seq.len(), 3);
1016        let removed = seq.remove(1);
1017        assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
1018        assert_eq!(seq.len(), 2);
1019
1020        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1021        assert_eq!(values, vec!["a", "c"]);
1022    }
1023
1024    #[test]
1025    fn test_flow_sequence_remove_first() {
1026        use crate::Document;
1027        let doc = Document::from_str("items: [a, b, c]").unwrap();
1028        let mapping = doc.as_mapping().unwrap();
1029        let seq = mapping.get_sequence("items").unwrap();
1030
1031        assert_eq!(seq.len(), 3);
1032        let removed = seq.remove(0);
1033        assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
1034        assert_eq!(seq.len(), 2);
1035
1036        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1037        assert_eq!(values, vec!["b", "c"]);
1038    }
1039
1040    #[test]
1041    fn test_flow_sequence_remove_last() {
1042        use crate::Document;
1043        let doc = Document::from_str("items: [a, b, c]").unwrap();
1044        let mapping = doc.as_mapping().unwrap();
1045        let seq = mapping.get_sequence("items").unwrap();
1046
1047        assert_eq!(seq.len(), 3);
1048        let removed = seq.remove(2);
1049        assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
1050        assert_eq!(seq.len(), 2);
1051
1052        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1053        assert_eq!(values, vec!["a", "b"]);
1054    }
1055
1056    #[test]
1057    fn test_flow_sequence_pop() {
1058        use crate::Document;
1059        let doc = Document::from_str("items: [a, b, c]").unwrap();
1060        let mapping = doc.as_mapping().unwrap();
1061        let seq = mapping.get_sequence("items").unwrap();
1062
1063        assert_eq!(seq.len(), 3);
1064        let popped = seq.pop().unwrap();
1065        assert_eq!(popped.to_string(), "c");
1066        assert_eq!(seq.len(), 2);
1067
1068        let popped = seq.pop().unwrap();
1069        assert_eq!(popped.to_string(), "b");
1070        assert_eq!(seq.len(), 1);
1071
1072        let popped = seq.pop().unwrap();
1073        assert_eq!(popped.to_string(), "a");
1074        assert_eq!(seq.len(), 0);
1075        assert!(seq.pop().is_none());
1076    }
1077
1078    #[test]
1079    fn test_flow_sequence_clear() {
1080        use crate::Document;
1081        let doc = Document::from_str("items: [a, b, c]").unwrap();
1082        let mapping = doc.as_mapping().unwrap();
1083        let seq = mapping.get_sequence("items").unwrap();
1084
1085        assert_eq!(seq.len(), 3);
1086        seq.clear();
1087        assert_eq!(seq.len(), 0);
1088        assert!(seq.is_empty());
1089    }
1090
1091    #[test]
1092    fn test_flow_sequence_with_whitespace() {
1093        use crate::Document;
1094        let doc = Document::from_str("items: [ a , b , c ]").unwrap();
1095        let mapping = doc.as_mapping().unwrap();
1096        let seq = mapping.get_sequence("items").unwrap();
1097
1098        assert_eq!(seq.len(), 3);
1099        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1100        assert_eq!(values, vec!["a", "b", "c"]);
1101    }
1102
1103    #[test]
1104    fn test_block_sequence_remove_middle() {
1105        use crate::Document;
1106        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1107        let mapping = doc.as_mapping().unwrap();
1108        let seq = mapping.get_sequence("items").unwrap();
1109
1110        assert_eq!(seq.len(), 3);
1111        let removed = seq.remove(1);
1112        assert_eq!(removed.map(|v| v.to_string()), Some("b".to_string()));
1113        assert_eq!(seq.len(), 2);
1114
1115        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1116        assert_eq!(values, vec!["a", "c"]);
1117    }
1118
1119    #[test]
1120    fn test_block_sequence_remove_first() {
1121        use crate::Document;
1122        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1123        let mapping = doc.as_mapping().unwrap();
1124        let seq = mapping.get_sequence("items").unwrap();
1125
1126        assert_eq!(seq.len(), 3);
1127        let removed = seq.remove(0);
1128        assert_eq!(removed.map(|v| v.to_string()), Some("a".to_string()));
1129        assert_eq!(seq.len(), 2);
1130
1131        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1132        assert_eq!(values, vec!["b", "c"]);
1133    }
1134
1135    #[test]
1136    fn test_block_sequence_remove_last() {
1137        use crate::Document;
1138        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1139        let mapping = doc.as_mapping().unwrap();
1140        let seq = mapping.get_sequence("items").unwrap();
1141
1142        assert_eq!(seq.len(), 3);
1143        let removed = seq.remove(2);
1144        assert_eq!(removed.map(|v| v.to_string()), Some("c".to_string()));
1145        assert_eq!(seq.len(), 2);
1146
1147        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1148        assert_eq!(values, vec!["a", "b"]);
1149    }
1150
1151    #[test]
1152    fn test_single_item_block_sequence_remove() {
1153        use crate::Document;
1154        let doc = Document::from_str("items:\n  - only").unwrap();
1155        let mapping = doc.as_mapping().unwrap();
1156        let seq = mapping.get_sequence("items").unwrap();
1157
1158        assert_eq!(seq.len(), 1);
1159        let removed = seq.remove(0);
1160        assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
1161        assert_eq!(seq.len(), 0);
1162    }
1163
1164    #[test]
1165    fn test_single_item_flow_sequence_remove() {
1166        use crate::Document;
1167        let doc = Document::from_str("items: [only]").unwrap();
1168        let mapping = doc.as_mapping().unwrap();
1169        let seq = mapping.get_sequence("items").unwrap();
1170
1171        assert_eq!(seq.len(), 1);
1172        let removed = seq.remove(0);
1173        assert_eq!(removed.map(|v| v.to_string()), Some("only".to_string()));
1174        assert_eq!(seq.len(), 0);
1175    }
1176
1177    #[test]
1178    fn test_flow_sequence_push() {
1179        use crate::Document;
1180        let doc = Document::from_str("items: [a, b]").unwrap();
1181        let mapping = doc.as_mapping().unwrap();
1182        let seq = mapping.get_sequence("items").unwrap();
1183
1184        assert_eq!(seq.len(), 2);
1185        seq.push("c");
1186        assert_eq!(seq.len(), 3);
1187
1188        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1189        assert_eq!(values, vec!["a", "b", "c"]);
1190    }
1191
1192    #[test]
1193    fn test_flow_sequence_push_multiple() {
1194        use crate::Document;
1195        let doc = Document::from_str("items: [a]").unwrap();
1196        let mapping = doc.as_mapping().unwrap();
1197        let seq = mapping.get_sequence("items").unwrap();
1198
1199        seq.push("b");
1200        seq.push("c");
1201        seq.push("d");
1202
1203        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1204        assert_eq!(values, vec!["a", "b", "c", "d"]);
1205    }
1206
1207    #[test]
1208    fn test_flow_sequence_set_item() {
1209        use crate::Document;
1210        let doc = Document::from_str("items: [a, b, c]").unwrap();
1211        let mapping = doc.as_mapping().unwrap();
1212        let seq = mapping.get_sequence("items").unwrap();
1213
1214        seq.set(1, "modified");
1215
1216        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1217        assert_eq!(values, vec!["a", "modified", "c"]);
1218    }
1219
1220    #[test]
1221    fn test_flow_sequence_insert_beginning() {
1222        use crate::Document;
1223        let doc = Document::from_str("items: [b, c]").unwrap();
1224        let mapping = doc.as_mapping().unwrap();
1225        let seq = mapping.get_sequence("items").unwrap();
1226
1227        seq.insert(0, "a");
1228
1229        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1230        assert_eq!(values, vec!["a", "b", "c"]);
1231    }
1232
1233    #[test]
1234    fn test_flow_sequence_insert_middle() {
1235        use crate::Document;
1236        let doc = Document::from_str("items: [a, c]").unwrap();
1237        let mapping = doc.as_mapping().unwrap();
1238        let seq = mapping.get_sequence("items").unwrap();
1239
1240        seq.insert(1, "b");
1241
1242        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1243        assert_eq!(values, vec!["a", "b", "c"]);
1244    }
1245
1246    #[test]
1247    fn test_flow_sequence_insert_end() {
1248        use crate::Document;
1249        let doc = Document::from_str("items: [a, b]").unwrap();
1250        let mapping = doc.as_mapping().unwrap();
1251        let seq = mapping.get_sequence("items").unwrap();
1252
1253        seq.insert(2, "c");
1254
1255        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1256        assert_eq!(values, vec!["a", "b", "c"]);
1257    }
1258
1259    #[test]
1260    fn test_block_sequence_push() {
1261        use crate::Document;
1262        let doc = Document::from_str("items:\n  - a\n  - b").unwrap();
1263        let mapping = doc.as_mapping().unwrap();
1264        let seq = mapping.get_sequence("items").unwrap();
1265
1266        assert_eq!(seq.len(), 2);
1267        seq.push("c");
1268        assert_eq!(seq.len(), 3);
1269
1270        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1271        assert_eq!(values, vec!["a", "b", "c"]);
1272    }
1273
1274    #[test]
1275    fn test_block_sequence_set_item() {
1276        use crate::Document;
1277        let doc = Document::from_str("items:\n  - a\n  - b\n  - c").unwrap();
1278        let mapping = doc.as_mapping().unwrap();
1279        let seq = mapping.get_sequence("items").unwrap();
1280
1281        seq.set(1, "modified");
1282
1283        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1284        assert_eq!(values, vec!["a", "modified", "c"]);
1285    }
1286
1287    #[test]
1288    fn test_block_sequence_insert_beginning() {
1289        use crate::Document;
1290        let doc = Document::from_str("items:\n  - b\n  - c").unwrap();
1291        let mapping = doc.as_mapping().unwrap();
1292        let seq = mapping.get_sequence("items").unwrap();
1293
1294        seq.insert(0, "a");
1295
1296        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1297        assert_eq!(values, vec!["a", "b", "c"]);
1298    }
1299
1300    #[test]
1301    fn test_block_sequence_insert_middle() {
1302        use crate::Document;
1303        let doc = Document::from_str("items:\n  - a\n  - c").unwrap();
1304        let mapping = doc.as_mapping().unwrap();
1305        let seq = mapping.get_sequence("items").unwrap();
1306
1307        seq.insert(1, "b");
1308
1309        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1310        assert_eq!(values, vec!["a", "b", "c"]);
1311    }
1312
1313    #[test]
1314    fn test_block_sequence_insert_end() {
1315        use crate::Document;
1316        let doc = Document::from_str("items:\n  - a\n  - b").unwrap();
1317        let mapping = doc.as_mapping().unwrap();
1318        let seq = mapping.get_sequence("items").unwrap();
1319
1320        seq.insert(2, "c");
1321
1322        let values: Vec<String> = seq.values().map(|v| v.to_string()).collect();
1323        assert_eq!(values, vec!["a", "b", "c"]);
1324    }
1325
1326    #[test]
1327    fn test_sequence_get_node() {
1328        let doc = YamlFile::from_str("items:\n  - alpha\n  - beta\n  - gamma")
1329            .unwrap()
1330            .document()
1331            .unwrap();
1332        let seq = doc.as_mapping().unwrap().get_sequence("items").unwrap();
1333
1334        assert_eq!(seq.len(), 3);
1335        assert!(seq.get(0).is_some());
1336        assert!(seq.get(1).is_some());
1337        assert!(seq.get(2).is_some());
1338        assert!(seq.get(3).is_none());
1339
1340        assert_eq!(
1341            seq.get(0).unwrap().as_scalar().unwrap().as_string(),
1342            "alpha"
1343        );
1344        assert_eq!(seq.get(1).unwrap().as_scalar().unwrap().as_string(), "beta");
1345        assert_eq!(
1346            seq.get(2).unwrap().as_scalar().unwrap().as_string(),
1347            "gamma"
1348        );
1349    }
1350
1351    #[test]
1352    fn test_sequence_set_with_nested_mapping() {
1353        use crate::path::YamlPath;
1354        use crate::Document;
1355
1356        let yaml_str = "items:\n  - name: first\n    value: 1\n  - name: second\n    value: 2\n";
1357        let doc = Document::from_str(yaml_str).unwrap();
1358
1359        let items_node = doc.get_path("items").unwrap();
1360        let items = items_node.as_sequence().unwrap();
1361
1362        assert_eq!(items.len(), 2);
1363        let first = items.get(0).unwrap();
1364        assert!(first.is_mapping());
1365
1366        items.set(0, "replaced");
1367        assert_eq!(
1368            doc.to_string(),
1369            "items:\n  - replaced\n  - name: second\n    value: 2\n"
1370        );
1371    }
1372}