mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 10:07:37 +00:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request 1. Create Apps and Bookmarks ut project. 2. Refactor Apps and Bookmarks. And some interface in these extensions to add a abstraction layer for testing purpose. New interface list: * ISettingsInterface * IUWPApplication * IAppCache * IBookmarkDataSource 3. Add/Migrate some test case for Apps and Bookmarks extension <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #41239 #41240 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
536 lines
17 KiB
C#
536 lines
17 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Collections.Generic;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
namespace Microsoft.CmdPal.Ext.Bookmarks.UnitTests;
|
|
|
|
[TestClass]
|
|
public class BookmarkJsonParserTests
|
|
{
|
|
private BookmarkJsonParser _parser;
|
|
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
_parser = new BookmarkJsonParser();
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_ValidJson_ReturnsBookmarks()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Google",
|
|
"Bookmark": "https://www.google.com"
|
|
},
|
|
{
|
|
"Name": "Local File",
|
|
"Bookmark": "C:\\temp\\file.txt"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(2, result.Data.Count);
|
|
Assert.AreEqual("Google", result.Data[0].Name);
|
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
|
Assert.AreEqual("Local File", result.Data[1].Name);
|
|
Assert.AreEqual("C:\\temp\\file.txt", result.Data[1].Bookmark);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_EmptyJson_ReturnsEmptyBookmarks()
|
|
{
|
|
// Arrange
|
|
var json = "{}";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_NullJson_ReturnsEmptyBookmarks()
|
|
{
|
|
// Act
|
|
var result = _parser.ParseBookmarks(null);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_WhitespaceJson_ReturnsEmptyBookmarks()
|
|
{
|
|
// Act
|
|
var result = _parser.ParseBookmarks(" ");
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_EmptyString_ReturnsEmptyBookmarks()
|
|
{
|
|
// Act
|
|
var result = _parser.ParseBookmarks(string.Empty);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_InvalidJson_ReturnsEmptyBookmarks()
|
|
{
|
|
// Arrange
|
|
var invalidJson = "{invalid json}";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(invalidJson);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_MalformedJson_ReturnsEmptyBookmarks()
|
|
{
|
|
// Arrange
|
|
var malformedJson = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Google",
|
|
"Bookmark": "https://www.google.com"
|
|
},
|
|
{
|
|
"Name": "Incomplete entry"
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(malformedJson);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(0, result.Data.Count);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_JsonWithTrailingCommas_ParsesSuccessfully()
|
|
{
|
|
// Arrange - JSON with trailing commas (should be handled by AllowTrailingCommas option)
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Google",
|
|
"Bookmark": "https://www.google.com",
|
|
},
|
|
{
|
|
"Name": "Local File",
|
|
"Bookmark": "C:\\temp\\file.txt",
|
|
},
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(2, result.Data.Count);
|
|
Assert.AreEqual("Google", result.Data[0].Name);
|
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_JsonWithDifferentCasing_ParsesSuccessfully()
|
|
{
|
|
// Arrange - JSON with different property name casing (should be handled by PropertyNameCaseInsensitive option)
|
|
var json = """
|
|
{
|
|
"data": [
|
|
{
|
|
"name": "Google",
|
|
"bookmark": "https://www.google.com"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(1, result.Data.Count);
|
|
Assert.AreEqual("Google", result.Data[0].Name);
|
|
Assert.AreEqual("https://www.google.com", result.Data[0].Bookmark);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SerializeBookmarks_ValidBookmarks_ReturnsJsonString()
|
|
{
|
|
// Arrange
|
|
var bookmarks = new Bookmarks
|
|
{
|
|
Data = new List<BookmarkData>
|
|
{
|
|
new BookmarkData { Name = "Google", Bookmark = "https://www.google.com" },
|
|
new BookmarkData { Name = "Local File", Bookmark = "C:\\temp\\file.txt" },
|
|
},
|
|
};
|
|
|
|
// Act
|
|
var result = _parser.SerializeBookmarks(bookmarks);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.IsTrue(result.Contains("Google"));
|
|
Assert.IsTrue(result.Contains("https://www.google.com"));
|
|
Assert.IsTrue(result.Contains("Local File"));
|
|
Assert.IsTrue(result.Contains("C:\\\\temp\\\\file.txt")); // Escaped backslashes in JSON
|
|
Assert.IsTrue(result.Contains("Data"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SerializeBookmarks_EmptyBookmarks_ReturnsValidJson()
|
|
{
|
|
// Arrange
|
|
var bookmarks = new Bookmarks();
|
|
|
|
// Act
|
|
var result = _parser.SerializeBookmarks(bookmarks);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.IsTrue(result.Contains("Data"));
|
|
Assert.IsTrue(result.Contains("[]"));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SerializeBookmarks_NullBookmarks_ReturnsEmptyString()
|
|
{
|
|
// Act
|
|
var result = _parser.SerializeBookmarks(null);
|
|
|
|
// Assert
|
|
Assert.AreEqual(string.Empty, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_RoundTripSerialization_PreservesData()
|
|
{
|
|
// Arrange
|
|
var originalBookmarks = new Bookmarks
|
|
{
|
|
Data = new List<BookmarkData>
|
|
{
|
|
new BookmarkData { Name = "Google", Bookmark = "https://www.google.com" },
|
|
new BookmarkData { Name = "Local File", Bookmark = "C:\\temp\\file.txt" },
|
|
new BookmarkData { Name = "Placeholder", Bookmark = "Open {file} in editor" },
|
|
},
|
|
};
|
|
|
|
// Act - Serialize then parse
|
|
var serializedJson = _parser.SerializeBookmarks(originalBookmarks);
|
|
var parsedBookmarks = _parser.ParseBookmarks(serializedJson);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(parsedBookmarks);
|
|
Assert.AreEqual(originalBookmarks.Data.Count, parsedBookmarks.Data.Count);
|
|
|
|
for (var i = 0; i < originalBookmarks.Data.Count; i++)
|
|
{
|
|
Assert.AreEqual(originalBookmarks.Data[i].Name, parsedBookmarks.Data[i].Name);
|
|
Assert.AreEqual(originalBookmarks.Data[i].Bookmark, parsedBookmarks.Data[i].Bookmark);
|
|
Assert.AreEqual(originalBookmarks.Data[i].IsPlaceholder, parsedBookmarks.Data[i].IsPlaceholder);
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_JsonWithPlaceholderBookmarks_CorrectlyIdentifiesPlaceholders()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Regular URL",
|
|
"Bookmark": "https://www.google.com"
|
|
},
|
|
{
|
|
"Name": "Placeholder Command",
|
|
"Bookmark": "notepad {file}"
|
|
},
|
|
{
|
|
"Name": "Multiple Placeholders",
|
|
"Bookmark": "copy {source} {destination}"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(3, result.Data.Count);
|
|
|
|
Assert.IsFalse(result.Data[0].IsPlaceholder);
|
|
Assert.IsTrue(result.Data[1].IsPlaceholder);
|
|
Assert.IsTrue(result.Data[2].IsPlaceholder);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_IsWebUrl_CorrectlyIdentifiesWebUrls()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "HTTPS Website",
|
|
"Bookmark": "https://www.google.com"
|
|
},
|
|
{
|
|
"Name": "HTTP Website",
|
|
"Bookmark": "http://example.com"
|
|
},
|
|
{
|
|
"Name": "Website without protocol",
|
|
"Bookmark": "www.github.com"
|
|
},
|
|
{
|
|
"Name": "Local File Path",
|
|
"Bookmark": "C:\\Users\\test\\Documents\\file.txt"
|
|
},
|
|
{
|
|
"Name": "Network Path",
|
|
"Bookmark": "\\\\server\\share\\file.txt"
|
|
},
|
|
{
|
|
"Name": "Executable",
|
|
"Bookmark": "notepad.exe"
|
|
},
|
|
{
|
|
"Name": "File URI",
|
|
"Bookmark": "file:///C:/temp/file.txt"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(7, result.Data.Count);
|
|
|
|
// Web URLs should return true
|
|
Assert.IsTrue(result.Data[0].IsWebUrl(), "HTTPS URL should be identified as web URL");
|
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "HTTP URL should be identified as web URL");
|
|
|
|
// This case will fail. We need to consider if we need to support pure domain value in bookmark.
|
|
// Assert.IsTrue(result.Data[2].IsWebUrl(), "Domain without protocol should be identified as web URL");
|
|
|
|
// Non-web URLs should return false
|
|
Assert.IsFalse(result.Data[3].IsWebUrl(), "Local file path should not be identified as web URL");
|
|
Assert.IsFalse(result.Data[4].IsWebUrl(), "Network path should not be identified as web URL");
|
|
Assert.IsFalse(result.Data[5].IsWebUrl(), "Executable should not be identified as web URL");
|
|
Assert.IsFalse(result.Data[6].IsWebUrl(), "File URI should not be identified as web URL");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_IsPlaceholder_CorrectlyIdentifiesPlaceholders()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Simple Placeholder",
|
|
"Bookmark": "notepad {file}"
|
|
},
|
|
{
|
|
"Name": "Multiple Placeholders",
|
|
"Bookmark": "copy {source} to {destination}"
|
|
},
|
|
{
|
|
"Name": "Web URL with Placeholder",
|
|
"Bookmark": "https://search.com?q={query}"
|
|
},
|
|
{
|
|
"Name": "Complex Placeholder",
|
|
"Bookmark": "cmd /c echo {message} > {output_file}"
|
|
},
|
|
{
|
|
"Name": "No Placeholder - Regular URL",
|
|
"Bookmark": "https://www.google.com"
|
|
},
|
|
{
|
|
"Name": "No Placeholder - Local File",
|
|
"Bookmark": "C:\\temp\\file.txt"
|
|
},
|
|
{
|
|
"Name": "False Positive - Only Opening Brace",
|
|
"Bookmark": "test { incomplete"
|
|
},
|
|
{
|
|
"Name": "False Positive - Only Closing Brace",
|
|
"Bookmark": "test } incomplete"
|
|
},
|
|
{
|
|
"Name": "Empty Placeholder",
|
|
"Bookmark": "command {}"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(9, result.Data.Count);
|
|
|
|
// Should be identified as placeholders
|
|
Assert.IsTrue(result.Data[0].IsPlaceholder, "Simple placeholder should be identified");
|
|
Assert.IsTrue(result.Data[1].IsPlaceholder, "Multiple placeholders should be identified");
|
|
Assert.IsTrue(result.Data[2].IsPlaceholder, "Web URL with placeholder should be identified");
|
|
Assert.IsTrue(result.Data[3].IsPlaceholder, "Complex placeholder should be identified");
|
|
Assert.IsTrue(result.Data[8].IsPlaceholder, "Empty placeholder should be identified");
|
|
|
|
// Should NOT be identified as placeholders
|
|
Assert.IsFalse(result.Data[4].IsPlaceholder, "Regular URL should not be placeholder");
|
|
Assert.IsFalse(result.Data[5].IsPlaceholder, "Local file should not be placeholder");
|
|
Assert.IsFalse(result.Data[6].IsPlaceholder, "Only opening brace should not be placeholder");
|
|
Assert.IsFalse(result.Data[7].IsPlaceholder, "Only closing brace should not be placeholder");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_MixedProperties_CorrectlyIdentifiesBothWebUrlAndPlaceholder()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "Web URL with Placeholder",
|
|
"Bookmark": "https://google.com/search?q={query}"
|
|
},
|
|
{
|
|
"Name": "Web URL without Placeholder",
|
|
"Bookmark": "https://github.com"
|
|
},
|
|
{
|
|
"Name": "Local File with Placeholder",
|
|
"Bookmark": "notepad {file}"
|
|
},
|
|
{
|
|
"Name": "Local File without Placeholder",
|
|
"Bookmark": "C:\\Windows\\notepad.exe"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(4, result.Data.Count);
|
|
|
|
// Web URL with placeholder
|
|
Assert.IsTrue(result.Data[0].IsWebUrl(), "Web URL with placeholder should be identified as web URL");
|
|
Assert.IsTrue(result.Data[0].IsPlaceholder, "Web URL with placeholder should be identified as placeholder");
|
|
|
|
// Web URL without placeholder
|
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "Web URL without placeholder should be identified as web URL");
|
|
Assert.IsFalse(result.Data[1].IsPlaceholder, "Web URL without placeholder should not be identified as placeholder");
|
|
|
|
// Local file with placeholder
|
|
Assert.IsFalse(result.Data[2].IsWebUrl(), "Local file with placeholder should not be identified as web URL");
|
|
Assert.IsTrue(result.Data[2].IsPlaceholder, "Local file with placeholder should be identified as placeholder");
|
|
|
|
// Local file without placeholder
|
|
Assert.IsFalse(result.Data[3].IsWebUrl(), "Local file without placeholder should not be identified as web URL");
|
|
Assert.IsFalse(result.Data[3].IsPlaceholder, "Local file without placeholder should not be identified as placeholder");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ParseBookmarks_EdgeCaseUrls_CorrectlyIdentifiesWebUrls()
|
|
{
|
|
// Arrange
|
|
var json = """
|
|
{
|
|
"Data": [
|
|
{
|
|
"Name": "FTP URL",
|
|
"Bookmark": "ftp://files.example.com"
|
|
},
|
|
{
|
|
"Name": "HTTPS with port",
|
|
"Bookmark": "https://localhost:8080"
|
|
},
|
|
{
|
|
"Name": "IP Address",
|
|
"Bookmark": "http://192.168.1.1"
|
|
},
|
|
{
|
|
"Name": "Subdomain",
|
|
"Bookmark": "https://api.github.com"
|
|
},
|
|
{
|
|
"Name": "Domain only",
|
|
"Bookmark": "example.com"
|
|
},
|
|
{
|
|
"Name": "Not a URL - no dots",
|
|
"Bookmark": "localhost"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
|
|
// Act
|
|
var result = _parser.ParseBookmarks(json);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(6, result.Data.Count);
|
|
|
|
Assert.IsFalse(result.Data[0].IsWebUrl(), "FTP URL should not be identified as web URL");
|
|
Assert.IsTrue(result.Data[1].IsWebUrl(), "HTTPS with port should be identified as web URL");
|
|
Assert.IsTrue(result.Data[2].IsWebUrl(), "IP Address with HTTP should be identified as web URL");
|
|
Assert.IsTrue(result.Data[3].IsWebUrl(), "Subdomain should be identified as web URL");
|
|
|
|
// This case will fail. We need to consider if we need to support pure domain value in bookmark.
|
|
// Assert.IsTrue(result.Data[4].IsWebUrl(), "Domain only should be identified as web URL");
|
|
Assert.IsFalse(result.Data[5].IsWebUrl(), "Single word without dots should not be identified as web URL");
|
|
}
|
|
}
|