refactor: organize test project structure
This commit is contained in:
1 parent
996c0a9515
commit
c6d16fd108
8 files changed
+106
-94
No files matched your search
@@ -1,6 +1,6 @@
|
||||
using SongTidy.Models;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="RenameFormats"/>.
|
||||
+8
-16
@@ -2,31 +2,23 @@ using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using SongTidy.Services;
|
||||
using SongTidy.Tests.TestUtilities;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="AudioFileScanner"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class AudioFileScannerTests {
|
||||
private string _tempDirectory = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a unique temporary directory for the current test.
|
||||
/// </summary>
|
||||
[TestInitialize]
|
||||
public void Initialize() {
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), $"SongTidy Scanner {Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
private readonly TempDirectory _tempDirectory = new();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the temporary directory created for the current test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void Cleanup() {
|
||||
Directory.Delete(_tempDirectory, recursive: true);
|
||||
_tempDirectory.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,7 +30,7 @@ public sealed class AudioFileScannerTests {
|
||||
CreateFile("b.FLAC");
|
||||
CreateFile("c.txt");
|
||||
|
||||
IReadOnlyList<string> files = AudioFileScanner.Scan(_tempDirectory);
|
||||
IReadOnlyList<string> files = AudioFileScanner.Scan(_tempDirectory.FullPath);
|
||||
|
||||
Assert.HasCount(2, files);
|
||||
Assert.IsTrue(files.Any(file => file.EndsWith("a.mp3", StringComparison.OrdinalIgnoreCase)));
|
||||
@@ -53,7 +45,7 @@ public sealed class AudioFileScannerTests {
|
||||
CreateFile("root.mp3");
|
||||
CreateFile(Path.Combine("sub", "nested.wav"));
|
||||
|
||||
IReadOnlyList<string> files = AudioFileScanner.Scan(_tempDirectory);
|
||||
IReadOnlyList<string> files = AudioFileScanner.Scan(_tempDirectory.FullPath);
|
||||
|
||||
Assert.HasCount(1, files);
|
||||
Assert.IsTrue(files[0].EndsWith("root.mp3", StringComparison.OrdinalIgnoreCase));
|
||||
@@ -64,8 +56,8 @@ public sealed class AudioFileScannerTests {
|
||||
/// </summary>
|
||||
/// <param name="fileName">The relative file name, which may include a subdirectory.</param>
|
||||
private void CreateFile(string fileName) {
|
||||
string path = Path.Combine(_tempDirectory, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory;
|
||||
string path = Path.Combine(_tempDirectory.FullPath, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory.FullPath;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, string.Empty);
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using SongTidy.Services;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="FilenameParser"/>.
|
||||
@@ -6,8 +6,9 @@ using System.Xml.Linq;
|
||||
|
||||
using SongTidy.Models;
|
||||
using SongTidy.Services;
|
||||
using SongTidy.Tests.TestUtilities;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="PlanXmlIO"/>.
|
||||
@@ -210,32 +211,27 @@ public sealed class PlanXmlIOTests {
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SavesAndLoadsFileWithSpacesInPath() {
|
||||
string directory = Path.Combine(Path.GetTempPath(), $"SongTidy Plan Test {Guid.NewGuid():N}");
|
||||
string filePath = Path.Combine(directory, "plan with spaces.xml");
|
||||
Directory.CreateDirectory(directory);
|
||||
using TempDirectory tempDirectory = new();
|
||||
string filePath = Path.Combine(tempDirectory.FullPath, "plan with spaces.xml");
|
||||
|
||||
try {
|
||||
SongPlan plan = CreateSamplePlan();
|
||||
SongPlan plan = CreateSamplePlan();
|
||||
|
||||
PlanXmlIO.Save(plan, filePath);
|
||||
PlanXmlIO.Save(plan, filePath);
|
||||
|
||||
Assert.IsTrue(File.Exists(filePath));
|
||||
Assert.IsTrue(File.Exists(filePath));
|
||||
|
||||
byte[] bytes = File.ReadAllBytes(filePath);
|
||||
Assert.AreEqual((byte)'<', bytes[0]);
|
||||
byte[] bytes = File.ReadAllBytes(filePath);
|
||||
Assert.AreEqual((byte)'<', bytes[0]);
|
||||
|
||||
string xml = Encoding.UTF8.GetString(bytes);
|
||||
Assert.IsTrue(
|
||||
xml.StartsWith("<?xml version=\"1.0\" encoding=\"utf-8\"?>", StringComparison.Ordinal),
|
||||
"The file should be written as UTF-8 without a BOM.");
|
||||
string xml = Encoding.UTF8.GetString(bytes);
|
||||
Assert.IsTrue(
|
||||
xml.StartsWith("<?xml version=\"1.0\" encoding=\"utf-8\"?>", StringComparison.Ordinal),
|
||||
"The file should be written as UTF-8 without a BOM.");
|
||||
|
||||
SongPlan loaded = PlanXmlIO.Load(filePath);
|
||||
Assert.AreEqual(plan.Format, loaded.Format);
|
||||
Assert.HasCount(plan.Items.Count, loaded.Items);
|
||||
AssertPlanItem(plan.Items[0], loaded.Items[0]);
|
||||
} finally {
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
SongPlan loaded = PlanXmlIO.Load(filePath);
|
||||
Assert.AreEqual(plan.Format, loaded.Format);
|
||||
Assert.HasCount(plan.Items.Count, loaded.Items);
|
||||
AssertPlanItem(plan.Items[0], loaded.Items[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
+10
-18
@@ -3,31 +3,23 @@ using System.Linq;
|
||||
|
||||
using SongTidy.Models;
|
||||
using SongTidy.Services;
|
||||
using SongTidy.Tests.TestUtilities;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="RenameValidator"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class RenameValidatorTests {
|
||||
private string _tempDirectory = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a unique temporary directory for the current test.
|
||||
/// </summary>
|
||||
[TestInitialize]
|
||||
public void Initialize() {
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), $"SongTidy Validation {Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
private readonly TempDirectory _tempDirectory = new();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the temporary directory created for the current test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void Cleanup() {
|
||||
Directory.Delete(_tempDirectory, recursive: true);
|
||||
_tempDirectory.Dispose();
|
||||
}
|
||||
|
||||
#region Per-entry validation
|
||||
@@ -105,7 +97,7 @@ public sealed class RenameValidatorTests {
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ReportsMissingSourceFile() {
|
||||
string sourcePath = Path.Combine(_tempDirectory, "Missing.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "Missing.mp3");
|
||||
|
||||
RenameValidationResult result = Validate(CreateItem(sourcePath, "Title"));
|
||||
|
||||
@@ -140,8 +132,8 @@ public sealed class RenameValidatorTests {
|
||||
[TestMethod]
|
||||
public void TreatsSourcePathsAsCaseSensitive() {
|
||||
CreateFile("Case.mp3");
|
||||
PlanItem first = CreateItem(Path.Combine(_tempDirectory, "Case.mp3"), "One");
|
||||
PlanItem second = CreateItem(Path.Combine(_tempDirectory, "case.mp3"), "Two");
|
||||
PlanItem first = CreateItem(Path.Combine(_tempDirectory.FullPath, "Case.mp3"), "One");
|
||||
PlanItem second = CreateItem(Path.Combine(_tempDirectory.FullPath, "case.mp3"), "Two");
|
||||
|
||||
RenameValidationResult result = Validate(first, second);
|
||||
|
||||
@@ -283,7 +275,7 @@ public sealed class RenameValidatorTests {
|
||||
public void ReportsAllProblemKinds() {
|
||||
string sourcePath = CreateFile("A.mp3");
|
||||
PlanItem emptyTitle = CreateItem(sourcePath, "");
|
||||
PlanItem missingSource = CreateItem(Path.Combine(_tempDirectory, "Missing.mp3"), "Title");
|
||||
PlanItem missingSource = CreateItem(Path.Combine(_tempDirectory.FullPath, "Missing.mp3"), "Title");
|
||||
PlanItem invalidCharacter = CreateItem(sourcePath, "Bad/Title");
|
||||
|
||||
RenameValidationResult result = Validate(emptyTitle, missingSource, invalidCharacter);
|
||||
@@ -319,8 +311,8 @@ public sealed class RenameValidatorTests {
|
||||
/// <param name="fileName">The relative file name, which may include a subdirectory.</param>
|
||||
/// <returns>The full path of the created file.</returns>
|
||||
private string CreateFile(string fileName) {
|
||||
string path = Path.Combine(_tempDirectory, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory;
|
||||
string path = Path.Combine(_tempDirectory.FullPath, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory.FullPath;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, string.Empty);
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace SongTidy.Tests.TestUtilities;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a unique temporary directory per test instance and removes it on disposal.
|
||||
/// </summary>
|
||||
public sealed class TempDirectory : IDisposable {
|
||||
/// <summary>
|
||||
/// Gets the unique directory path managed by this instance.
|
||||
/// </summary>
|
||||
public string FullPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a unique temporary directory.
|
||||
/// </summary>
|
||||
public TempDirectory() {
|
||||
FullPath = Path.Combine(Path.GetTempPath(), "songtidy-tests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(FullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the directory tree, retrying once after a short delay.
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
try {
|
||||
// SAFETY: Recursive deletion is confined to the GUID-named directory
|
||||
// created by this instance under the OS temp folder.
|
||||
Directory.Delete(FullPath, recursive: true);
|
||||
} catch (IOException) {
|
||||
// A file handle may still be closing; retry once before giving up.
|
||||
Thread.Sleep(100);
|
||||
|
||||
try {
|
||||
// SAFETY: The retry targets the same instance-owned temp directory.
|
||||
Directory.Delete(FullPath, recursive: true);
|
||||
} catch (IOException) {
|
||||
// Leftovers in the OS temp folder are harmless; never fail a test over cleanup.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-36
@@ -3,32 +3,24 @@ using System.Linq;
|
||||
|
||||
using SongTidy.Models;
|
||||
using SongTidy.Services;
|
||||
using SongTidy.Tests.TestUtilities;
|
||||
using SongTidy.ViewModels;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="MainWindowViewModel"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class MainWindowViewModelTests {
|
||||
private string _tempDirectory = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a unique temporary directory for the current test.
|
||||
/// </summary>
|
||||
[TestInitialize]
|
||||
public void Initialize() {
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), $"SongTidy ViewModel {Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
private readonly TempDirectory _tempDirectory = new();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the temporary directory created for the current test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void Cleanup() {
|
||||
Directory.Delete(_tempDirectory, recursive: true);
|
||||
_tempDirectory.Dispose();
|
||||
}
|
||||
|
||||
#region Input and merge rules
|
||||
@@ -39,7 +31,7 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void AddFilesParsesAndAppendsEntries() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A - B (Live).mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
||||
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
|
||||
@@ -62,7 +54,7 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void AddFilesSkipsDuplicatesAndPreservesEdits() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A - B.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.Entries[0].Artist = "Edited";
|
||||
|
||||
@@ -80,8 +72,8 @@ public sealed class MainWindowViewModelTests {
|
||||
MainWindowViewModel viewModel = new();
|
||||
|
||||
viewModel.AddFiles(new[] {
|
||||
Path.Combine(_tempDirectory, "a.mp3"),
|
||||
Path.Combine(_tempDirectory, "A.mp3"),
|
||||
Path.Combine(_tempDirectory.FullPath, "a.mp3"),
|
||||
Path.Combine(_tempDirectory.FullPath, "A.mp3"),
|
||||
});
|
||||
|
||||
Assert.HasCount(2, viewModel.Entries);
|
||||
@@ -93,9 +85,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void AddFilesWithOnlyDuplicatesDoesNotDirty() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
|
||||
@@ -113,7 +105,7 @@ public sealed class MainWindowViewModelTests {
|
||||
CreateFile("C.txt");
|
||||
CreateFile(Path.Combine("sub", "D.wav"));
|
||||
|
||||
viewModel.AddFilesFromFolder(_tempDirectory);
|
||||
viewModel.AddFilesFromFolder(_tempDirectory.FullPath);
|
||||
|
||||
Assert.HasCount(2, viewModel.Entries);
|
||||
}
|
||||
@@ -128,9 +120,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void EntryEditMarksDirtyAndRevertingDoesNotClear() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
SongEntryViewModel entry = viewModel.Entries[0];
|
||||
string originalArtist = entry.Artist;
|
||||
|
||||
@@ -147,9 +139,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void FormatChangeUpdatesPreviewsAndMarksDirty() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A - B (Live).mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
|
||||
viewModel.SelectedFormat = RenameFormat.TitleFirst;
|
||||
|
||||
@@ -163,9 +155,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void SettingSameFormatDoesNotDirty() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
|
||||
viewModel.SelectedFormat = RenameFormat.ArtistFirst;
|
||||
|
||||
@@ -182,9 +174,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void NewPlanClearsDocument() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
|
||||
viewModel.NewPlan();
|
||||
|
||||
@@ -200,9 +192,9 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void ClosePlanClearsDocument() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A.mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory, "plan.xml"));
|
||||
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
||||
|
||||
viewModel.ClosePlan();
|
||||
|
||||
@@ -217,17 +209,17 @@ public sealed class MainWindowViewModelTests {
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void LoadPlanRestoresDocument() {
|
||||
string planPath = Path.Combine(_tempDirectory, "plan.xml");
|
||||
string planPath = Path.Combine(_tempDirectory.FullPath, "plan.xml");
|
||||
SongPlan plan = new() {
|
||||
Format = RenameFormat.TitleFirst,
|
||||
Items = new List<PlanItem> {
|
||||
new PlanItem {
|
||||
Path = Path.Combine(_tempDirectory, "A - B.mp3"),
|
||||
Path = Path.Combine(_tempDirectory.FullPath, "A - B.mp3"),
|
||||
Artist = "A",
|
||||
Title = "B",
|
||||
},
|
||||
new PlanItem {
|
||||
Path = Path.Combine(_tempDirectory, "C.mp3"),
|
||||
Path = Path.Combine(_tempDirectory.FullPath, "C.mp3"),
|
||||
Title = "C",
|
||||
Comment = "Live",
|
||||
},
|
||||
@@ -253,10 +245,10 @@ public sealed class MainWindowViewModelTests {
|
||||
[TestMethod]
|
||||
public void SavePlanWritesDocumentAndClearsDirty() {
|
||||
MainWindowViewModel viewModel = new();
|
||||
string sourcePath = Path.Combine(_tempDirectory, "A - B (Live).mp3");
|
||||
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
||||
viewModel.AddFiles(new[] { sourcePath });
|
||||
viewModel.SelectedFormat = RenameFormat.TitleFirst;
|
||||
string planPath = Path.Combine(_tempDirectory, "saved.xml");
|
||||
string planPath = Path.Combine(_tempDirectory.FullPath, "saved.xml");
|
||||
|
||||
viewModel.SavePlan(planPath);
|
||||
|
||||
@@ -306,8 +298,8 @@ public sealed class MainWindowViewModelTests {
|
||||
/// <param name="fileName">The relative file name, which may include a subdirectory.</param>
|
||||
/// <returns>The full path of the created file.</returns>
|
||||
private string CreateFile(string fileName) {
|
||||
string path = Path.Combine(_tempDirectory, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory;
|
||||
string path = Path.Combine(_tempDirectory.FullPath, fileName);
|
||||
string directory = Path.GetDirectoryName(path) ?? _tempDirectory.FullPath;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
File.WriteAllText(path, string.Empty);
|
||||
+1
-1
@@ -3,7 +3,7 @@ using System.IO;
|
||||
using SongTidy.Models;
|
||||
using SongTidy.ViewModels;
|
||||
|
||||
namespace SongTidy.Tests;
|
||||
namespace SongTidy.Tests.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="SongEntryViewModel"/>.
|
||||
Reference in new issue
Block a user