/ src / modules / cmdpal / Tests / Microsoft.CmdPal.Ext.Bookmarks.UnitTests / PlaceholderParserTests.cs
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 }