Skip to content

Commit a33bfa0

Browse files
committed
test: Cover remaining CombinedTag and FlacTag members
The primary TDD cycle only exercised fields needed for the Issue #6 fix (Title/Genre on CombinedTag; Title/Album/Pictures on FlacTag). Codecov's patch target is ~93%, and the untouched fields were dragging the patch coverage to 75%. These are characterization tests that lock in the expected behavior of the remaining members. CombinedTagTests (+7): - All string fields follow first-non-empty selection - Track follows first-non-null (uint?) pattern - TagType aggregates flags across members via bitwise OR - Pictures unions+dedups across tags (mirrors FlacTag behavior) - Setters on the base facade are no-ops (format subclasses override) - Render returns empty; Clear is a no-op FlacTagTests (+4): - All text fields (Artist/Year/Comment/Genre/Track) delegate to VorbisComment and auto-create it on first write - TagType advertises Xiph | FlacMetadata - Render and Clear are view-level no-ops - Pictures setter accepts null without throwing
1 parent 02d9d18 commit a33bfa0

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

tests/TagLibSharp2.Tests/Core/CombinedTagTests.cs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using TagLibSharp2.Core;
55
using TagLibSharp2.Id3;
66
using TagLibSharp2.Id3.Id3v2;
7+
using TagLibSharp2.Xiph;
78

89
namespace TagLibSharp2.Tests.Core;
910

@@ -68,4 +69,126 @@ public void Title_SkipsNullTagsInPriorityList ()
6869

6970
Assert.AreEqual ("From Secondary", combined.Title);
7071
}
72+
73+
/// <summary>
74+
/// All string fields follow the same first-non-empty selection across the
75+
/// priority list. This test locks in the uniform behavior rather than
76+
/// relying on only Title/Genre from earlier tests.
77+
/// </summary>
78+
[TestMethod]
79+
public void AllStringFields_ReturnFirstNonEmptyValueAcrossTags ()
80+
{
81+
var primary = new Id3v2Tag { Artist = "Primary Artist" };
82+
var secondary = new Id3v1Tag {
83+
Album = "Secondary Album",
84+
Year = "2001",
85+
Comment = "Secondary Comment",
86+
Genre = "Rock", // must be a valid ID3v1 genre (https://id3.org/ID3v1)
87+
};
88+
89+
var combined = new CombinedTag (primary, secondary);
90+
91+
Assert.AreEqual ("Primary Artist", combined.Artist, "Artist from primary");
92+
Assert.AreEqual ("Secondary Album", combined.Album, "Album falls back to secondary");
93+
Assert.AreEqual ("2001", combined.Year, "Year falls back to secondary");
94+
Assert.AreEqual ("Secondary Comment", combined.Comment, "Comment falls back to secondary");
95+
Assert.AreEqual ("Rock", combined.Genre, "Genre falls back to secondary");
96+
}
97+
98+
/// <summary>
99+
/// Track (uint?) follows the same first-non-null pattern as string fields.
100+
/// </summary>
101+
[TestMethod]
102+
public void Track_ReturnsFirstNonNullValueAcrossTags ()
103+
{
104+
var primary = new Id3v2Tag ();
105+
var secondary = new Id3v1Tag { Track = 7 };
106+
107+
var combined = new CombinedTag (primary, secondary);
108+
109+
Assert.AreEqual ((uint)7, combined.Track);
110+
}
111+
112+
/// <summary>
113+
/// <see cref="CombinedTag.TagType"/> aggregates the tag type flags of every
114+
/// non-null member. This lets callers check "does this file have Xiph data?"
115+
/// via a single bitwise query.
116+
/// </summary>
117+
[TestMethod]
118+
public void TagType_AggregatesFlagsAcrossMembers ()
119+
{
120+
var combined = new CombinedTag (new Id3v2Tag (), new Id3v1Tag ());
121+
122+
Assert.AreEqual (TagTypes.Id3v2 | TagTypes.Id3v1, combined.TagType);
123+
}
124+
125+
/// <summary>
126+
/// <see cref="CombinedTag.Pictures"/> unions pictures across member tags and
127+
/// deduplicates on (PictureType, MimeType, PictureData). The dedup behavior
128+
/// mirrors FlacTag's and is the library convention (see also TagLib C++,
129+
/// Jaudiotagger).
130+
/// </summary>
131+
[TestMethod]
132+
public void Pictures_UnionsAcrossTagsWithDedup ()
133+
{
134+
var sharedBytes = new byte[] { 0x01, 0x02 };
135+
var primary = new Id3v2Tag ();
136+
primary.Pictures = [new FlacPicture ("image/jpeg", PictureType.FrontCover, "",
137+
new BinaryData (sharedBytes), 0, 0, 0, 0)];
138+
var secondary = new Id3v2Tag ();
139+
secondary.Pictures = [
140+
new FlacPicture ("image/jpeg", PictureType.FrontCover, "",
141+
new BinaryData (sharedBytes), 0, 0, 0, 0),
142+
new FlacPicture ("image/jpeg", PictureType.BackCover, "",
143+
new BinaryData (new byte[] { 0x03 }), 0, 0, 0, 0),
144+
];
145+
146+
var combined = new CombinedTag (primary, secondary);
147+
148+
Assert.AreEqual (2, combined.Pictures.Length,
149+
"duplicate front cover across tags dedupes; distinct back cover remains");
150+
}
151+
152+
/// <summary>
153+
/// The base <see cref="CombinedTag"/> exposes writes as no-ops so that it
154+
/// cannot accidentally mutate underlying tags without a subclass making an
155+
/// explicit decision. Format-specific subclasses override individual
156+
/// setters when write-through is the correct behavior.
157+
/// </summary>
158+
[TestMethod]
159+
public void Setters_AreNoOpsOnBaseFacade ()
160+
{
161+
var primary = new Id3v2Tag { Title = "Original" };
162+
var combined = new CombinedTag (primary);
163+
164+
combined.Title = "Changed";
165+
combined.Track = 42;
166+
combined.Pictures = [];
167+
168+
Assert.AreEqual ("Original", primary.Title, "Base CombinedTag.Title setter does not mutate members");
169+
Assert.IsNull (primary.Track, "Base CombinedTag.Track setter does not mutate members");
170+
}
171+
172+
[TestMethod]
173+
public void Render_ReturnsEmptyBinaryData ()
174+
{
175+
var combined = new CombinedTag (new Id3v2Tag { Title = "x" });
176+
177+
var rendered = combined.Render ();
178+
179+
Assert.IsTrue (rendered.IsEmpty,
180+
"CombinedTag is a view; it does not own bytes and Render returns empty");
181+
}
182+
183+
[TestMethod]
184+
public void Clear_IsNoOp ()
185+
{
186+
var primary = new Id3v2Tag { Title = "Keep Me" };
187+
var combined = new CombinedTag (primary);
188+
189+
combined.Clear ();
190+
191+
Assert.AreEqual ("Keep Me", primary.Title,
192+
"Base CombinedTag.Clear does not mutate members");
193+
}
71194
}

tests/TagLibSharp2.Tests/Xiph/FlacTagTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,80 @@ public void MediaFile_ReadFromData_Flac_TagPicturesMatchFilePictures ()
214214
"MediaFileResult.Tag.Pictures must match File.Pictures (Issue #6)");
215215
}
216216

217+
/// <summary>
218+
/// All text fields read through <see cref="FlacFile.Tag"/> reflect the
219+
/// VorbisComment's values, and writes create a VorbisComment if none exists.
220+
/// Extends <see cref="Tag_TextFields_ReadAndWriteThroughVorbisComment"/> to
221+
/// every abstract string/uint field defined on <see cref="Tag"/>.
222+
/// </summary>
223+
[TestMethod]
224+
public void Tag_AllTextFields_DelegateToVorbisComment ()
225+
{
226+
var file = FlacFile.Read (TestBuilders.Flac.CreateMinimal ()).File!;
227+
Assert.IsNull (file.VorbisComment, "precondition: no VorbisComment yet");
228+
229+
// Writes auto-create the VorbisComment, then all subsequent reads match.
230+
file.Tag!.Artist = "A";
231+
file.Tag.Year = "2026";
232+
file.Tag.Comment = "C";
233+
file.Tag.Genre = "Rock";
234+
file.Tag.Track = 3;
235+
236+
Assert.IsNotNull (file.VorbisComment, "VorbisComment created on first write");
237+
Assert.AreEqual ("A", file.Tag.Artist);
238+
Assert.AreEqual ("2026", file.Tag.Year);
239+
Assert.AreEqual ("C", file.Tag.Comment);
240+
Assert.AreEqual ("Rock", file.Tag.Genre);
241+
Assert.AreEqual ((uint)3, file.Tag.Track);
242+
}
243+
244+
/// <summary>
245+
/// <see cref="FlacTag.TagType"/> advertises both Xiph (the comment block)
246+
/// and FlacMetadata (the native PICTURE/CueSheet blocks it also fronts).
247+
/// </summary>
248+
[TestMethod]
249+
public void Tag_TagType_AdvertisesXiphAndFlacMetadata ()
250+
{
251+
var file = FlacFile.Read (TestBuilders.Flac.CreateMinimal ()).File!;
252+
253+
Assert.AreEqual (TagTypes.Xiph | TagTypes.FlacMetadata, file.Tag!.TagType);
254+
}
255+
256+
/// <summary>
257+
/// <see cref="FlacTag"/> is a view over the file — it is not itself a
258+
/// serializable block — so <c>Render</c> returns empty and <c>Clear</c>
259+
/// is a no-op. Callers that need to persist changes do so through
260+
/// <see cref="FlacFile.Render"/>.
261+
/// </summary>
262+
[TestMethod]
263+
public void Tag_RenderAndClear_AreViewLevelNoOps ()
264+
{
265+
var data = TestBuilders.Flac.CreateWithVorbisComment ("Keep Me", "Keep Artist");
266+
var file = FlacFile.Read (data).File!;
267+
268+
Assert.IsTrue (file.Tag!.Render ().IsEmpty, "Render on the view returns empty");
269+
270+
file.Tag.Clear ();
271+
272+
Assert.AreEqual ("Keep Me", file.VorbisComment!.Title,
273+
"Clear on the view does not wipe the underlying VorbisComment");
274+
}
275+
276+
/// <summary>
277+
/// Setting Tag.Pictures to null clears pictures rather than throwing.
278+
/// </summary>
279+
[TestMethod]
280+
public void Tag_SetPictures_NullValueClearsWithoutThrowing ()
281+
{
282+
var data = TestBuilders.Flac.CreateWithPicture (PictureType.FrontCover);
283+
var file = FlacFile.Read (data).File!;
284+
Assert.AreEqual (1, file.Pictures.Count, "precondition: one picture");
285+
286+
file.Tag!.Pictures = null!;
287+
288+
Assert.AreEqual (0, file.Pictures.Count);
289+
}
290+
217291
static byte[] BuildFlacWithPictureInBothLocations ()
218292
{
219293
var seed = TestBuilders.Flac.CreateWithVorbisComment ("seed", "seed");

0 commit comments

Comments
 (0)