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
@@ -0,0 +1,309 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System.IO;
|
||||
|
||||
using SongTidy.Models;
|
||||
using SongTidy.ViewModels;
|
||||
|
||||
namespace SongTidy.Tests.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for <see cref="SongEntryViewModel"/>.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public sealed class SongEntryViewModelTests {
|
||||
#region Preview
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the initial fields and target preview.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void InitializesFieldsAndTargetPreview() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
|
||||
Assert.AreEqual("Artist", entry.Artist);
|
||||
Assert.AreEqual("Title", entry.Title);
|
||||
Assert.AreEqual("Live", entry.Comment);
|
||||
Assert.AreEqual(RenameFormat.ArtistFirst, entry.Format);
|
||||
Assert.AreEqual("A.mp3", entry.OriginalFileName);
|
||||
Assert.AreEqual("Artist - Title (Live).mp3", entry.TargetFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that changing a field immediately updates the target preview.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void UpdatesTargetPreviewWhenFieldChanges() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
|
||||
entry.Artist = "New Artist";
|
||||
|
||||
Assert.AreEqual("New Artist - Title (Live).mp3", entry.TargetFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that changing the format updates the target preview.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void UpdatesTargetPreviewWhenFormatChanges() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
|
||||
entry.Format = RenameFormat.TitleFirst;
|
||||
|
||||
Assert.AreEqual("Title (Live) - Artist.mp3", entry.TargetFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the original extension is preserved for other file types.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void PreservesOriginalExtension() {
|
||||
SongEntryViewModel entry = new(
|
||||
Path.Combine("Music", "A.flac"),
|
||||
string.Empty,
|
||||
"Title",
|
||||
string.Empty,
|
||||
RenameFormat.ArtistFirst);
|
||||
|
||||
Assert.AreEqual("Title.flac", entry.TargetFileName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dirty notifications
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that editing any field raises the edited event.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RaisesEditedWhenFieldChanges() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
int editCount = 0;
|
||||
entry.Edited += (_, _) => editCount++;
|
||||
|
||||
entry.Artist = "New Artist";
|
||||
entry.Title = "New Title";
|
||||
entry.Comment = "New Comment";
|
||||
|
||||
Assert.AreEqual(3, editCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning the current value does not raise the edited event.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void DoesNotRaiseEditedWhenFieldValueIsUnchanged() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
int editCount = 0;
|
||||
entry.Edited += (_, _) => editCount++;
|
||||
|
||||
entry.Artist = entry.Artist;
|
||||
|
||||
Assert.AreEqual(0, editCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that changing the format does not itself count as a field edit.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ChangingFormatDoesNotRaiseEdited() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
int editCount = 0;
|
||||
entry.Edited += (_, _) => editCount++;
|
||||
|
||||
entry.Format = RenameFormat.TitleFirst;
|
||||
|
||||
Assert.AreEqual(0, editCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Swap
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that swapping exchanges the artist and title values.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SwapsArtistAndTitle() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
|
||||
entry.SwapArtistAndTitle();
|
||||
|
||||
Assert.AreEqual("Title", entry.Artist);
|
||||
Assert.AreEqual("Artist", entry.Title);
|
||||
Assert.AreEqual("Title - Artist (Live).mp3", entry.TargetFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that swapping raises edited notifications for both changed fields.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void SwapRaisesEditedForBothFields() {
|
||||
SongEntryViewModel entry = CreateEntry();
|
||||
int editCount = 0;
|
||||
entry.Edited += (_, _) => editCount++;
|
||||
|
||||
entry.SwapArtistAndTitle();
|
||||
|
||||
Assert.AreEqual(2, editCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a standard test entry.
|
||||
/// </summary>
|
||||
/// <returns>The created entry.</returns>
|
||||
private static SongEntryViewModel CreateEntry() {
|
||||
return new SongEntryViewModel(
|
||||
Path.Combine("Music", "A.mp3"),
|
||||
"Artist",
|
||||
"Title",
|
||||
"Live",
|
||||
RenameFormat.ArtistFirst);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user