PlaceholderParserTests.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Collections.Generic;
  7  using System.Linq;
  8  using Microsoft.CmdPal.Ext.Bookmarks.Services;
  9  using Microsoft.VisualStudio.TestTools.UnitTesting;
 10  
 11  namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
 12  
 13  [TestClass]
 14  public class PlaceholderParserTests
 15  {
 16      private IPlaceholderParser _parser;
 17  
 18      [TestInitialize]
 19      public void Setup()
 20      {
 21          _parser = new PlaceholderParser();
 22      }
 23  
 24      public static IEnumerable<object[]> ValidPlaceholderTestData =>
 25      [
 26          [
 27              "Hello {name}!",
 28                  true,
 29                  "Hello ",
 30                  new[] { "name" },
 31                  new[] { 6 }
 32          ],
 33          [
 34              "User {user_name} has {count} items",
 35                  true,
 36                  "User ",
 37                  new[] { "user_name", "count" },
 38                  new[] { 5, 21 }
 39          ],
 40          [
 41              "Order {order-id} for {name} by {name}",
 42                  true,
 43                  "Order ",
 44                  new[] { "order-id", "name", "name" },
 45                  new[] { 6, 21, 31 }
 46          ],
 47          [
 48              "{start} and {end}",
 49                  true,
 50                  string.Empty,
 51                  new[] { "start", "end" },
 52                  new[] { 0, 12 }
 53          ],
 54          [
 55              "Number {123} and text {abc}",
 56                  true,
 57                  "Number ",
 58                  new[] { "123", "abc" },
 59                  new[] { 7, 22 }
 60          ]
 61      ];
 62  
 63      public static IEnumerable<object[]> InvalidPlaceholderTestData =>
 64      [
 65          [string.Empty, false, string.Empty, Array.Empty<string>()],
 66          ["No placeholders here", false, "No placeholders here", Array.Empty<string>()],
 67          ["GUID: {550e8400-e29b-41d4-a716-446655440000}", false, "GUID: {550e8400-e29b-41d4-a716-446655440000}", Array.Empty<string>()],
 68          ["Invalid {user.name} placeholder", false, "Invalid {user.name} placeholder", Array.Empty<string>()],
 69          ["Empty {} placeholder", false, "Empty {} placeholder", Array.Empty<string>()],
 70          ["Unclosed {placeholder", false, "Unclosed {placeholder", Array.Empty<string>()],
 71          ["No opening brace placeholder}", false, "No opening brace placeholder}", Array.Empty<string>()],
 72          ["Invalid chars {user@domain}", false, "Invalid chars {user@domain}", Array.Empty<string>()],
 73          ["Spaces { name }", false, "Spaces { name }", Array.Empty<string>()]
 74      ];
 75  
 76      [TestMethod]
 77      [DynamicData(nameof(ValidPlaceholderTestData))]
 78      public void ParsePlaceholders_ValidInput_ReturnsExpectedResults(
 79          string input,
 80          bool expectedResult,
 81          string expectedHead,
 82          string[] expectedPlaceholderNames,
 83          int[] expectedIndexes)
 84      {
 85          // Act
 86          var result = _parser.ParsePlaceholders(input, out var head, out var placeholders);
 87  
 88          // Assert
 89          Assert.AreEqual(expectedResult, result);
 90          Assert.AreEqual(expectedHead, head);
 91          Assert.AreEqual(expectedPlaceholderNames.Length, placeholders.Count);
 92  
 93          var actualNames = placeholders.Select(p => p.Name).ToArray();
 94          var actualIndexes = placeholders.Select(p => p.Index).ToArray();
 95  
 96          // Validate names and indexes (allow duplicates, ignore order)
 97          CollectionAssert.AreEquivalent(expectedPlaceholderNames, actualNames);
 98          CollectionAssert.AreEquivalent(expectedIndexes, actualIndexes);
 99  
100          // Validate name-index pairing exists for each expected placeholder occurrence
101          for (var i = 0; i < expectedPlaceholderNames.Length; i++)
102          {
103              var expectedName = expectedPlaceholderNames[i];
104              var expectedIndex = expectedIndexes[i];
105              Assert.IsTrue(
106                  placeholders.Any(p => p.Name == expectedName && p.Index == expectedIndex),
107                  $"Expected placeholder '{{{expectedName}}}' at index {expectedIndex} was not found.");
108          }
109      }
110  
111      [TestMethod]
112      [DynamicData(nameof(InvalidPlaceholderTestData))]
113      public void ParsePlaceholders_InvalidInput_ReturnsExpectedResults(
114          string input,
115          bool expectedResult,
116          string expectedHead,
117          string[] expectedPlaceholderNames)
118      {
119          // Act
120          var result = _parser.ParsePlaceholders(input, out var head, out var placeholders);
121  
122          // Assert
123          Assert.AreEqual(expectedResult, result);
124          Assert.AreEqual(expectedHead, head);
125          Assert.AreEqual(expectedPlaceholderNames.Length, placeholders.Count);
126  
127          var actualNames = placeholders.Select(p => p.Name).ToArray();
128          CollectionAssert.AreEquivalent(expectedPlaceholderNames, actualNames);
129      }
130  
131      [TestMethod]
132      public void ParsePlaceholders_NullInput_ThrowsArgumentNullException()
133      {
134          Assert.ThrowsException<ArgumentNullException>(() => _parser.ParsePlaceholders(null!, out _, out _));
135      }
136  
137      [TestMethod]
138      public void Placeholder_Equality_WorksCorrectly()
139      {
140          // Arrange
141          var placeholder1 = new PlaceholderInfo("name", 0);
142          var placeholder2 = new PlaceholderInfo("name", 0);
143          var placeholder3 = new PlaceholderInfo("other", 0);
144          var placeholder4 = new PlaceholderInfo("name", 1);
145  
146          // Assert
147          Assert.AreEqual(placeholder1, placeholder2);
148          Assert.AreNotEqual(placeholder1, placeholder3);
149          Assert.AreEqual(placeholder1.GetHashCode(), placeholder2.GetHashCode());
150          Assert.AreNotEqual(placeholder1, placeholder4);
151          Assert.AreNotEqual(placeholder1.GetHashCode(), placeholder4.GetHashCode());
152      }
153  
154      [TestMethod]
155      public void Placeholder_ToString_ReturnsName()
156      {
157          // Arrange
158          var placeholder = new PlaceholderInfo("userName", 0);
159  
160          // Assert
161          Assert.AreEqual("userName", placeholder.ToString());
162      }
163  
164      [TestMethod]
165      public void Placeholder_Constructor_ThrowsOnNull()
166      {
167          // Assert
168          Assert.ThrowsException<ArgumentNullException>(() => new PlaceholderInfo(null!, 0));
169      }
170  
171      [TestMethod]
172      public void Placeholder_Constructor_ThrowsArgumentOutOfRange()
173      {
174          // Assert
175          Assert.ThrowsException<ArgumentOutOfRangeException>(() => new PlaceholderInfo("Name", -1));
176      }
177  }