using SongTidy.Services; namespace SongTidy.Tests; /// /// Contains tests for . /// [TestClass] public sealed class FilenameParserTests { #region Successful parsing /// /// Verifies the documented example with one English and one Chinese bracket group. /// [TestMethod] public void ParsesSpecificationExample() { AssertParse("A - B (Live - Tokyo)(修正版)", "A", "B", "Live - Tokyo 修正版"); } /// /// Verifies that multiple comment groups are joined with a single half-width space. /// [TestMethod] public void ParsesMultipleCommentGroups() { AssertParse("A - B (Live)(Remix)", "A", "B", "Live Remix"); } /// /// Verifies that Chinese bracket groups are extracted like English bracket groups. /// [TestMethod] public void ParsesChineseBracketComment() { AssertParse("歌手 - 歌名(现场)", "歌手", "歌名", "现场"); } /// /// Verifies that all comment groups are removed from the text used for artist and title. /// [TestMethod] public void RemovesAllCommentGroupsFromSource() { AssertParse("A - B (Live) (Tokyo)", "A", "B", "Live Tokyo"); } /// /// Verifies that nested brackets stay as literal text inside the comment. /// [TestMethod] public void KeepsNestedBracketsInsideComment() { AssertParse("A - B (Live (Tokyo) Mix)", "A", "B", "Live (Tokyo) Mix"); } /// /// Verifies that nested full-width brackets stay as literal text inside an English comment. /// [TestMethod] public void PreservesFullWidthNestedBrackets() { AssertParse("A - B (Live(东京))", "A", "B", "Live(东京)"); } /// /// Verifies that only the outermost pair of a nested group is treated as the comment. /// [TestMethod] public void TreatsOnlyOutermostBracketGroupAsComment() { AssertParse("A - B ((Live))", "A", "B", "(Live)"); } /// /// Verifies that a comment can be extracted from a name without a hyphen. /// [TestMethod] public void ParsesCommentWithoutArtistSeparator() { AssertParse("Song (Live)", string.Empty, "Song", "Live"); } #endregion #region Invalid bracket structures /// /// Verifies that an unmatched opening bracket disables comment extraction entirely. /// [TestMethod] public void AbandonsExtractionForUnmatchedOpeningBracket() { AssertParse("A - B (Live", "A", "B (Live", string.Empty); } /// /// Verifies that an unmatched closing bracket disables comment extraction entirely. /// [TestMethod] public void AbandonsExtractionForUnmatchedClosingBracket() { AssertParse("A - B Live)", "A", "B Live)", string.Empty); } /// /// Verifies that opening and closing brackets of different types cannot pair. /// [TestMethod] public void AbandonsExtractionForMismatchedBracketTypes() { AssertParse("A - B (Live)", "A", "B (Live)", string.Empty); } /// /// Verifies that an extra closing bracket after a valid pair disables extraction entirely. /// [TestMethod] public void AbandonsExtractionForExtraClosingBracket() { AssertParse("A - B (Live))", "A", "B (Live))", string.Empty); } /// /// Verifies that an extra opening bracket after a valid pair disables extraction entirely. /// [TestMethod] public void AbandonsExtractionForExtraOpeningBracket() { AssertParse("A - B (Live) (", "A", "B (Live) (", string.Empty); } /// /// Verifies that an unclosed outer bracket invalidates otherwise complete nested brackets. /// [TestMethod] public void AbandonsExtractionForUnclosedOuterBracket() { AssertParse("A - B (Live (Tokyo)", "A", "B (Live (Tokyo)", string.Empty); } /// /// Verifies that crossing bracket groups are treated as an invalid structure. /// [TestMethod] public void AbandonsExtractionForCrossingBracketGroups() { AssertParse("(a(b))", string.Empty, "(a(b))", string.Empty); } #endregion #region Hyphen handling /// /// Verifies that a single half-width hyphen separates artist from title. /// [TestMethod] public void SplitsSingleHalfWidthHyphen() { AssertParse("A - B", "A", "B", string.Empty); } /// /// Verifies that multiple half-width hyphens make the whole remaining text the title. /// [TestMethod] public void TreatsMultipleHyphensAsTitleOnly() { AssertParse("A - B - C", string.Empty, "A - B - C", string.Empty); } /// /// Verifies that hyphens inside a removed comment do not count when applying the multiple-hyphen rule. /// [TestMethod] public void IgnoresHyphensInsideCommentsWhenSplitting() { AssertParse("A - B (Live - Tokyo - Mix)", "A", "B", "Live - Tokyo - Mix"); } /// /// Verifies that a full-width hyphen is not treated as a separator. /// [TestMethod] public void DoesNotSplitOnFullWidthHyphen() { AssertParse("A - B", string.Empty, "A - B", string.Empty); } /// /// Verifies that an em dash is not treated as a separator. /// [TestMethod] public void DoesNotSplitOnEmDash() { AssertParse("A — B", string.Empty, "A — B", string.Empty); } /// /// Verifies that a name without a hyphen produces an empty artist and a full title. /// [TestMethod] public void ReturnsEmptyArtistWhenNoHyphenExists() { AssertParse("Song Title", string.Empty, "Song Title", string.Empty); } /// /// Verifies that an empty input produces three empty fields. /// [TestMethod] public void ReturnsEmptyFieldsForEmptyInput() { AssertParse(string.Empty, string.Empty, string.Empty, string.Empty); } #endregion #region Whitespace handling /// /// Verifies that standard whitespace around the artist and title is removed. /// [TestMethod] public void TrimsStandardWhitespaceAroundArtistAndTitle() { AssertParse(" A - B ", "A", "B", string.Empty); } /// /// Verifies that standard whitespace around extracted comment text is removed. /// [TestMethod] public void TrimsStandardWhitespaceInsideComments() { AssertParse("A - B ( Live )", "A", "B", "Live"); } /// /// Verifies that full-width spaces inside comment text are preserved. /// [TestMethod] public void PreservesFullWidthSpacesInsideComments() { AssertParse("A - B (\u3000Live\u3000)", "A", "B", "\u3000Live\u3000"); } /// /// Verifies that full-width spaces around artist and title are preserved. /// [TestMethod] public void PreservesFullWidthSpacesAroundFields() { AssertParse("A\u3000-\u3000B", "A\u3000", "\u3000B", string.Empty); } #endregion #region Argument validation /// /// Verifies that a null input is rejected. /// [TestMethod] public void ThrowsForNullInput() { Assert.Throws(() => FilenameParser.Parse(null!)); } #endregion /// /// Parses the input and asserts all three resulting fields. /// /// The file name to parse. /// The expected artist value. /// The expected title value. /// The expected comment value. private static void AssertParse( string input, string expectedArtist, string expectedTitle, string expectedComment) { FilenameParseResult result = FilenameParser.Parse(input); Assert.AreEqual(expectedArtist, result.Artist, "Artist mismatch."); Assert.AreEqual(expectedTitle, result.Title, "Title mismatch."); Assert.AreEqual(expectedComment, result.Comment, "Comment mismatch."); } }