310 lines
10 KiB
C#
310 lines
10 KiB
C#
using System.IO;
|
|
using System.Linq;
|
|
|
|
using SongTidy.Models;
|
|
using SongTidy.Services;
|
|
using SongTidy.Tests.TestUtilities;
|
|
using SongTidy.ViewModels;
|
|
|
|
namespace SongTidy.Tests.ViewModels;
|
|
|
|
/// <summary>
|
|
/// Contains tests for <see cref="MainWindowViewModel"/>.
|
|
/// </summary>
|
|
[TestClass]
|
|
public sealed class MainWindowViewModelTests {
|
|
private readonly TempDirectory _tempDirectory = new();
|
|
|
|
/// <summary>
|
|
/// Removes the temporary directory created for the current test.
|
|
/// </summary>
|
|
[TestCleanup]
|
|
public void Cleanup() {
|
|
_tempDirectory.Dispose();
|
|
}
|
|
|
|
#region Input and merge rules
|
|
|
|
/// <summary>
|
|
/// Verifies that added files are parsed and appended.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void AddFilesParsesAndAppendsEntries() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
|
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
|
|
Assert.HasCount(1, viewModel.Entries);
|
|
Assert.AreEqual(1, viewModel.EntryCount);
|
|
Assert.IsTrue(viewModel.IsDirty);
|
|
|
|
SongEntryViewModel entry = viewModel.Entries[0];
|
|
Assert.AreEqual(sourcePath, entry.FilePath);
|
|
Assert.AreEqual("A", entry.Artist);
|
|
Assert.AreEqual("B", entry.Title);
|
|
Assert.AreEqual("Live", entry.Comment);
|
|
Assert.AreEqual("A - B (Live).mp3", entry.OriginalFileName);
|
|
Assert.AreEqual("A - B (Live).mp3", entry.TargetFileName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that duplicate paths keep the existing entry and its edits.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void AddFilesSkipsDuplicatesAndPreservesEdits() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.Entries[0].Artist = "Edited";
|
|
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
|
|
Assert.HasCount(1, viewModel.Entries);
|
|
Assert.AreEqual("Edited", viewModel.Entries[0].Artist);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that duplicate detection compares full path strings case-sensitively.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void AddFilesTreatsPathsAsCaseSensitive() {
|
|
MainWindowViewModel viewModel = new();
|
|
|
|
viewModel.AddFiles(new[] {
|
|
Path.Combine(_tempDirectory.FullPath, "a.mp3"),
|
|
Path.Combine(_tempDirectory.FullPath, "A.mp3"),
|
|
});
|
|
|
|
Assert.HasCount(2, viewModel.Entries);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that adding only duplicates does not change the dirty state.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void AddFilesWithOnlyDuplicatesDoesNotDirty() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that folder input uses the audio extension filter.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void AddFilesFromFolderAddsOnlyAudioFiles() {
|
|
MainWindowViewModel viewModel = new();
|
|
CreateFile("A.mp3");
|
|
CreateFile("B.FLAC");
|
|
CreateFile("C.txt");
|
|
CreateFile(Path.Combine("sub", "D.wav"));
|
|
|
|
viewModel.AddFilesFromFolder(_tempDirectory.FullPath);
|
|
|
|
Assert.HasCount(2, viewModel.Entries);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Dirty tracking
|
|
|
|
/// <summary>
|
|
/// Verifies that field edits mark the document dirty and reverting does not clear it.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void EntryEditMarksDirtyAndRevertingDoesNotClear() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
SongEntryViewModel entry = viewModel.Entries[0];
|
|
string originalArtist = entry.Artist;
|
|
|
|
entry.Artist = "Changed";
|
|
Assert.IsTrue(viewModel.IsDirty);
|
|
|
|
entry.Artist = originalArtist;
|
|
Assert.IsTrue(viewModel.IsDirty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that changing the format updates previews and marks the document dirty.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void FormatChangeUpdatesPreviewsAndMarksDirty() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
|
|
viewModel.SelectedFormat = RenameFormat.TitleFirst;
|
|
|
|
Assert.IsTrue(viewModel.IsDirty);
|
|
Assert.AreEqual("B (Live) - A.mp3", viewModel.Entries[0].TargetFileName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that selecting the current format does not mark the document dirty.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void SettingSameFormatDoesNotDirty() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
|
|
viewModel.SelectedFormat = RenameFormat.ArtistFirst;
|
|
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Plan operations
|
|
|
|
/// <summary>
|
|
/// Verifies that starting a new plan clears entries, path, and dirty state.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void NewPlanClearsDocument() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
|
|
viewModel.NewPlan();
|
|
|
|
Assert.HasCount(0, viewModel.Entries);
|
|
Assert.AreEqual(0, viewModel.EntryCount);
|
|
Assert.IsNull(viewModel.PlanPath);
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that closing a plan clears entries, path, and dirty state.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ClosePlanClearsDocument() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A.mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SavePlan(Path.Combine(_tempDirectory.FullPath, "plan.xml"));
|
|
|
|
viewModel.ClosePlan();
|
|
|
|
Assert.HasCount(0, viewModel.Entries);
|
|
Assert.AreEqual(0, viewModel.EntryCount);
|
|
Assert.IsNull(viewModel.PlanPath);
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that loading a plan restores its format, entries, and path.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void LoadPlanRestoresDocument() {
|
|
string planPath = Path.Combine(_tempDirectory.FullPath, "plan.xml");
|
|
SongPlan plan = new() {
|
|
Format = RenameFormat.TitleFirst,
|
|
Items = new List<PlanItem> {
|
|
new PlanItem {
|
|
Path = Path.Combine(_tempDirectory.FullPath, "A - B.mp3"),
|
|
Artist = "A",
|
|
Title = "B",
|
|
},
|
|
new PlanItem {
|
|
Path = Path.Combine(_tempDirectory.FullPath, "C.mp3"),
|
|
Title = "C",
|
|
Comment = "Live",
|
|
},
|
|
},
|
|
};
|
|
PlanXmlIO.Save(plan, planPath);
|
|
|
|
MainWindowViewModel viewModel = new();
|
|
viewModel.LoadPlan(planPath);
|
|
|
|
Assert.AreEqual(RenameFormat.TitleFirst, viewModel.SelectedFormat);
|
|
Assert.HasCount(2, viewModel.Entries);
|
|
Assert.AreEqual(2, viewModel.EntryCount);
|
|
Assert.AreEqual(planPath, viewModel.PlanPath);
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
Assert.AreEqual("B - A.mp3", viewModel.Entries[0].TargetFileName);
|
|
Assert.AreEqual("C (Live).mp3", viewModel.Entries[1].TargetFileName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies saving to a path and then using the existing-path save overload.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void SavePlanWritesDocumentAndClearsDirty() {
|
|
MainWindowViewModel viewModel = new();
|
|
string sourcePath = Path.Combine(_tempDirectory.FullPath, "A - B (Live).mp3");
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.SelectedFormat = RenameFormat.TitleFirst;
|
|
string planPath = Path.Combine(_tempDirectory.FullPath, "saved.xml");
|
|
|
|
viewModel.SavePlan(planPath);
|
|
|
|
Assert.AreEqual(planPath, viewModel.PlanPath);
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
|
|
SongPlan saved = PlanXmlIO.Load(planPath);
|
|
Assert.AreEqual(RenameFormat.TitleFirst, saved.Format);
|
|
Assert.HasCount(1, saved.Items);
|
|
Assert.AreEqual("A", saved.Items[0].Artist);
|
|
Assert.AreEqual("B", saved.Items[0].Title);
|
|
Assert.AreEqual("Live", saved.Items[0].Comment);
|
|
|
|
viewModel.Entries[0].Comment = "Changed";
|
|
Assert.IsTrue(viewModel.IsDirty);
|
|
Assert.IsTrue(viewModel.SavePlan());
|
|
Assert.IsFalse(viewModel.IsDirty);
|
|
|
|
SongPlan reloaded = PlanXmlIO.Load(planPath);
|
|
Assert.AreEqual("Changed", reloaded.Items[0].Comment);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Validation
|
|
|
|
/// <summary>
|
|
/// Verifies that validation uses the current entries.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ValidateRenameUsesCurrentDocument() {
|
|
string sourcePath = CreateFile("A.mp3");
|
|
MainWindowViewModel viewModel = new();
|
|
viewModel.AddFiles(new[] { sourcePath });
|
|
viewModel.Entries[0].Title = string.Empty;
|
|
|
|
RenameValidationResult result = viewModel.ValidateRename();
|
|
|
|
Assert.IsTrue(result.Issues.Any(issue => issue.Problem == RenameValidationProblem.EmptyTitle));
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Creates an empty file inside the test directory.
|
|
/// </summary>
|
|
/// <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.FullPath, fileName);
|
|
string directory = Path.GetDirectoryName(path) ?? _tempDirectory.FullPath;
|
|
|
|
Directory.CreateDirectory(directory);
|
|
File.WriteAllText(path, string.Empty);
|
|
|
|
return path;
|
|
}
|
|
}
|