diff --git a/src/id3v2/frames/commentsFrame.ts b/src/id3v2/frames/commentsFrame.ts index bbb11a49..4151bbd9 100644 --- a/src/id3v2/frames/commentsFrame.ts +++ b/src/id3v2/frames/commentsFrame.ts @@ -22,25 +22,6 @@ export default class CommentsFrame extends Frame { super(frameHeader); } - /** - * Constructs and initializes a new CommentsFrame from a description - * @param description Description of the new frame - * @param language Optional, ISO-639-2 language code for the new frame - * @param encoding Optional, text encoding to use when rendering the new frame - */ - public static fromDescription( - description: string, - language?: string, - encoding: StringType = Id3v2Settings.defaultEncoding - ): CommentsFrame { - const frame = new CommentsFrame(new Id3v2FrameHeader(FrameIdentifiers.COMM)); - frame.textEncoding = encoding; - frame._language = language; - frame._description = description; - - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -85,6 +66,31 @@ export default class CommentsFrame extends Frame { return frame; } + /** + * Constructs and initializes a new CommentsFrame from a description + * @param description Optional, description of the comment being created. If omitted, defaults + * to `""`. + * @param text Optional, text of the comment being created. If omitted, defaults to `""`. + * @param language Optional, ISO-639-2 language code for the new frame. If omitted, defaults to + * `XXX`. @TODO: Should this default to unk? + * @param encoding Optional, text encoding to use when rendering the new frame. If omitted, + * defaults to {@link Id3v2Settings.defaultEncoding}. + */ + public static fromFields( + description?: string, + text?: string, + language?: string, + encoding?: StringType + ): CommentsFrame { + const frame = new CommentsFrame(new Id3v2FrameHeader(FrameIdentifiers.COMM)); + frame._description = description ?? ""; + frame._language = language ?? "XXX"; + frame._text = text ?? ""; + frame._textEncoding = encoding ?? Id3v2Settings.defaultEncoding; + + return frame; + } + // #endregion // #region Public Properties @@ -145,9 +151,13 @@ export default class CommentsFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = CommentsFrame.fromDescription(this._description, this._language, this._textEncoding); - frame._text = this._text; - return frame; + const clone = new CommentsFrame(new Id3v2FrameHeader(FrameIdentifiers.COMM)); + clone._description = this._description; + clone._language = this._language; + clone._text = this._text; + clone._textEncoding = this._textEncoding; + + return clone; } /** diff --git a/src/id3v2/frames/eventTimeCodeFrame.ts b/src/id3v2/frames/eventTimeCodeFrame.ts index c729279a..facb2484 100644 --- a/src/id3v2/frames/eventTimeCodeFrame.ts +++ b/src/id3v2/frames/eventTimeCodeFrame.ts @@ -89,16 +89,6 @@ export class EventTimeCodeFrame extends Frame { super(header); } - /** - * Constructs and initializes a new instance without contents - */ - public static fromEmpty(): EventTimeCodeFrame { - // @TODO: Should we be mucking around with the flags like this? - const frame = new EventTimeCodeFrame(new Id3v2FrameHeader(FrameIdentifiers.ETCO)); - frame.flags = Id3v2FrameFlags.FileAlterPreservation; - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -153,12 +143,18 @@ export class EventTimeCodeFrame extends Frame { /** * Constructs and initializes a timestamp format set - * @param timestampFormat Timestamp format for the event codes stored in this frame + * @param timestampFormat Optional, timestamp format for the event codes stored in this frame. + * If omitted, defaults to {@link TimestampFormat.Unknown}. + * @param events Optional, list of events to store in the current frame. If omitted, defaults + * to `[]`. */ - public static fromTimestampFormat(timestampFormat: TimestampFormat): EventTimeCodeFrame { + public static fromFields(timestampFormat?: TimestampFormat, events?: EventTimeCode[]): EventTimeCodeFrame { const frame = new EventTimeCodeFrame(new Id3v2FrameHeader(FrameIdentifiers.ETCO)); - frame.flags = Id3v2FrameFlags.FileAlterPreservation; - frame.timestampFormat = timestampFormat; + frame.flags = Id3v2FrameFlags.FileAlterPreservation; // @TODO: Should we be mucking around with flags like this? + + frame._timestampFormat = timestampFormat ?? TimestampFormat.Unknown; + frame._events = events ?? []; + return frame; } @@ -170,7 +166,7 @@ export class EventTimeCodeFrame extends Frame { * Gets the event this frame contains. Each {@link EventTimeCode} represents a single event at a * certain point in time. */ - public get events(): EventTimeCode[] { return this._events || []; } + public get events(): EventTimeCode[] { return this._events ?? []; } /** * Sets the event this frame contains */ @@ -196,9 +192,10 @@ export class EventTimeCodeFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = new EventTimeCodeFrame(this.header); - frame.timestampFormat = this.timestampFormat; - frame.events = this.events.map((i) => i.clone()); + const frame = new EventTimeCodeFrame(new Id3v2FrameHeader(this.frameId, this.flags)); + frame._events = this._events.map(i => i.clone()); + frame._timestampFormat = this._timestampFormat; + return frame; } diff --git a/src/id3v2/frames/genreFrame.ts b/src/id3v2/frames/genreFrame.ts index a4bf6078..d0d597dd 100644 --- a/src/id3v2/frames/genreFrame.ts +++ b/src/id3v2/frames/genreFrame.ts @@ -2,10 +2,10 @@ import Frame from "./frame"; import Genres from "../../genres"; import Id3v2Settings from "../id3v2Settings"; import {ByteVector, StringType} from "../../byteVector"; +import {CorruptFileError} from "../../errors"; import {Id3v2FrameHeader} from "./frameHeader"; import {FrameIdentifiers} from "../frameIdentifiers"; import {ArrayUtils, Guards, StringUtils} from "../../utils"; -import {CorruptFileError} from "../../errors"; /** * This class provides support for ID3v2 TCON content type frames. @@ -17,7 +17,7 @@ export default class GenreFrame extends Frame { private static readonly REMIX_STRING = "Remix"; private _encoding: StringType = Id3v2Settings.defaultEncoding; - private _textFields: string[] = []; + private _textFields: string[]; // #region Constructors @@ -25,19 +25,6 @@ export default class GenreFrame extends Frame { super(header); } - /** - * Constructs and initializes a new instance. - * @param encoding Optionally, the encoding to use for the new instance. If omitted, defaults - * to {@link Id3v2Settings.defaultEncoding} - */ - public static fromEncoding( - encoding: StringType = Id3v2Settings.defaultEncoding - ): GenreFrame { - const frame = new GenreFrame(new Id3v2FrameHeader(FrameIdentifiers.TCON)); - frame._encoding = encoding; - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -131,20 +118,33 @@ export default class GenreFrame extends Frame { return frame; } + /** + * Constructs and initializes a new instance. + * @param text Optional, the genres to store in the new instance. If omitted, defaults to an + * empty array. + * @param encoding Optional, the encoding to use for the new instance. If omitted, defaults to + * {@link Id3v2Settings.defaultEncoding}. + */ + public static fromFields(text?: string[], encoding?: StringType): GenreFrame { + const frame = new GenreFrame(new Id3v2FrameHeader(FrameIdentifiers.TCON)); + frame._textFields = text ?? []; + frame._encoding = encoding ?? Id3v2Settings.defaultEncoding; + + return frame; + } + // #endregion // #region Properties /** * Gets the genres contained in the current instance. - * Note: Modifying the contents of the returned value will not modify the contents of the - * current instance. The value must be reassigned for the value to change. */ - public get text(): string[] { return this._textFields.slice(); } + public get text(): string[] { return this._textFields; } /** * Sets the genres contained in the current instance. */ - public set text(value: string[]) { this._textFields = value ? value.slice() : []; } + public set text(value: string[]) { this._textFields = value ?? []; } /** * Gets the text encoding to use when rendering the current instance. @@ -167,9 +167,7 @@ export default class GenreFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = GenreFrame.fromEncoding(this._encoding); - frame._textFields = this._textFields.slice(); - return frame; + return GenreFrame.fromFields(this._textFields.slice(), this._encoding); } /** diff --git a/src/id3v2/frames/musicCdIdentifierFrame.ts b/src/id3v2/frames/musicCdIdentifierFrame.ts index cf10e0eb..6df87460 100644 --- a/src/id3v2/frames/musicCdIdentifierFrame.ts +++ b/src/id3v2/frames/musicCdIdentifierFrame.ts @@ -17,16 +17,6 @@ export default class MusicCdIdentifierFrame extends Frame { super(header); } - /** - * Constructs and initializes a new instance with a specified bytes. - * @param data Contents of the frame - */ - public static fromData(data: ByteVector): MusicCdIdentifierFrame { - const frame = new MusicCdIdentifierFrame(new Id3v2FrameHeader(FrameIdentifiers.MCDI)); - frame._data = data.toByteVector(); - return frame; - } - /** * Constructs and initialized a new instance by storing the bytes of the frame. * @param header Header of the frame @@ -47,6 +37,18 @@ export default class MusicCdIdentifierFrame extends Frame { return frame; } + /** + * Constructs and initializes a new instance with the specified bytes. + * @param data Optional, contents of the frame. If omitted, defaults to an empty + * {@link ByteVector}. + */ + public static fromFields(data?: ByteVector): MusicCdIdentifierFrame { + const frame = new MusicCdIdentifierFrame(new Id3v2FrameHeader(FrameIdentifiers.MCDI)); + frame._data = data ?? ByteVector.empty(); + + return frame; + } + /** * Gets the identifier data stored in the current instance */ @@ -64,9 +66,7 @@ export default class MusicCdIdentifierFrame extends Frame { /** @inheritDoc */ public clone(): MusicCdIdentifierFrame { - const frame = new MusicCdIdentifierFrame(new Id3v2FrameHeader(FrameIdentifiers.MCDI)); - frame.data = this._data.toByteVector(); - return frame; + return MusicCdIdentifierFrame.fromFields(this._data.toByteVector()); } /** @inheritDoc */ diff --git a/src/id3v2/frames/playCountFrame.ts b/src/id3v2/frames/playCountFrame.ts index c9ee1c0b..83b22bf2 100644 --- a/src/id3v2/frames/playCountFrame.ts +++ b/src/id3v2/frames/playCountFrame.ts @@ -1,9 +1,9 @@ import Frame from "./frame"; import {ByteVector} from "../../byteVector"; +import {CorruptFileError, NotSupportedError} from "../../errors"; import {Id3v2FrameHeader} from "./frameHeader"; import {FrameIdentifiers} from "../frameIdentifiers"; import {ArrayUtils, Guards} from "../../utils"; -import {CorruptFileError, NotSupportedError} from "../../errors"; /** * This class extends {@link Frame} implementing support for ID3v2 play count (PCNT) frames. @@ -18,13 +18,6 @@ export default class PlayCountFrame extends Frame { // #region Constructors - /** - * Constructs and initializes a new instance with a count of zero - */ - public static fromEmpty(): PlayCountFrame { - return new PlayCountFrame(new Id3v2FrameHeader(FrameIdentifiers.PCNT)); - } - /** * Constructs and initialized a new instance by parsing values from the field data. * @param header Header of the frame @@ -50,6 +43,20 @@ export default class PlayCountFrame extends Frame { return frame; } + /** + * Constructs and initializes a new instance with a play count. + * @param playCount Optional, number of times the track has been played. If omitted, defaults + * to `0`. + */ + public static fromFields(playCount: bigint = BigInt(0)): PlayCountFrame { + Guards.ulong(playCount, "playCount"); + + const frame = new PlayCountFrame(new Id3v2FrameHeader(FrameIdentifiers.PCNT)); + frame._playCount = playCount; + + return frame; + } + // #endregion // #region Public Properties diff --git a/src/id3v2/frames/popularimeterFrame.ts b/src/id3v2/frames/popularimeterFrame.ts index 21aae722..2a962432 100644 --- a/src/id3v2/frames/popularimeterFrame.ts +++ b/src/id3v2/frames/popularimeterFrame.ts @@ -11,7 +11,7 @@ import {ArrayUtils, Guards} from "../../utils"; export default class PopularimeterFrame extends Frame { private _playCount: bigint; private _rating: number; - private _user: string = ""; + private _user: string; // #region Constructors @@ -70,11 +70,19 @@ export default class PopularimeterFrame extends Frame { /** * Constructs and initializes a new instance for a specified user with a rating and play count * of zero. - * @param user Email of the user that gave the rating + * @param user Optional, email of the user that gave the rating. If omitted, defaults to `""`. + * @param rating Optional, rating of the track. If omitted, defaults to 0. + * @param playCount Optional, number of times the track has been played. */ - public static fromUser(user: string): PopularimeterFrame { + public static fromFields(user?: string, rating?: number, playCount?: bigint): PopularimeterFrame { + Guards.byteOptional(rating, "rating"); + Guards.ulongOptional(playCount, "playCount"); + const frame = new PopularimeterFrame(new Id3v2FrameHeader(FrameIdentifiers.POPM)); - frame._user = user; + frame._playCount = playCount; + frame._rating = rating ?? 0; + frame._user = user ?? ""; + return frame; } @@ -127,10 +135,7 @@ export default class PopularimeterFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = PopularimeterFrame.fromUser(this.user); - frame.playCount = this.playCount; - frame.rating = this.rating; - return frame; + return PopularimeterFrame.fromFields(this._user, this._rating, this._playCount); } /** @inheritDoc */ diff --git a/src/id3v2/frames/privateFrame.ts b/src/id3v2/frames/privateFrame.ts index 5a0b9519..3eff87b7 100644 --- a/src/id3v2/frames/privateFrame.ts +++ b/src/id3v2/frames/privateFrame.ts @@ -50,12 +50,15 @@ export default class PrivateFrame extends Frame { /** * Constructs and initializes a new instance with the provided owner - * @param owner Owner of the private frame + * @param owner Optional, owner of the private frame. If omitted, defaults to `""`. + * @param privateData Optional, private data contained in the frame. If omitted, defaults to + * an empty {@link ByteVector}. */ - public static fromOwner(owner: string): PrivateFrame { + public static fromFields(owner?: string, privateData?: ByteVector): PrivateFrame { const frame = new PrivateFrame(new Id3v2FrameHeader(FrameIdentifiers.PRIV)); - frame._owner = owner; - frame._privateData = ByteVector.empty(); + frame._owner = owner ?? ""; + frame._privateData = privateData ?? ByteVector.empty(); + return frame; } @@ -88,9 +91,7 @@ export default class PrivateFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = PrivateFrame.fromOwner(this._owner); - frame._privateData = this._privateData.toByteVector(); - return frame; + return PrivateFrame.fromFields(this._owner, this._privateData.toByteVector()); } /** @inheritDoc */ diff --git a/src/id3v2/frames/relativeVolumeFrame.ts b/src/id3v2/frames/relativeVolumeFrame.ts index 608a8fe4..24863795 100644 --- a/src/id3v2/frames/relativeVolumeFrame.ts +++ b/src/id3v2/frames/relativeVolumeFrame.ts @@ -157,6 +157,19 @@ export class ChannelData { this._volumeAdjustment = Math.floor(value * 512); } + /** + * Creates and returns a duplicate instance of the current object. + * @return {ChannelData} New instance of ChannelData with the same properties as the original + */ + public clone(): ChannelData { + const clone = new ChannelData(this._channel); + clone._peakBits = this._peakBits; + clone._peakVolume = this._peakVolume; + clone._volumeAdjustment = this._volumeAdjustment; + + return clone; + } + /** * Generates a raw byte representation of the current instance. */ @@ -186,15 +199,18 @@ export class ChannelData { // @TODO: RVA2 only exists in v2.4, RVAD exists in v2.3, but it is a different format. export class RelativeVolumeFrame extends Frame { // @TODO: Get rid of this whole "is set" malarky and just store a map of channels that are set. - private readonly _channels: ChannelData[] = new Array(9); + private _channels: ChannelData[]; private _identification: string; // #region Constructors private constructor(header: Id3v2FrameHeader) { super(header); + + // Initialize the channel data array + this._channels = []; for (let i = 0; i < 9; i++) { - this._channels[i] = new ChannelData(i); + this._channels.push(new ChannelData(i)); } } @@ -254,12 +270,27 @@ export class RelativeVolumeFrame extends Frame { } /** - * Constructs and initializes a new instance with a specified identifier - * @param identification Identification ot use for the new frame + * Constructs and initializes a new instance with the specified fields. + * @param identification Optional, identification to use for the new frame. If omitted, + * defaults to `""`. + * @param channels Optional, channel data for the new frame. If provided, the provided channel + * data will replace the empty channel data (and duplicates will overwrite each other). If + * not provided, the internal channel data will be unset for all channels. */ - public static fromIdentification(identification: string): RelativeVolumeFrame { + public static fromFields(identification?: string, channels?: ChannelData[]): RelativeVolumeFrame { const frame = new RelativeVolumeFrame(new Id3v2FrameHeader(FrameIdentifiers.RVA2)); - frame._identification = identification; + frame._identification = identification ?? ""; + if (!!channels) { + // Replace blank channel data with the provided channel data. + for (const channel of channels) { + if (!channel) { + continue; + } + + frame._channels[channel.channelType] = channel; + } + } + return frame; } @@ -271,10 +302,11 @@ export class RelativeVolumeFrame extends Frame { * Gets the channels in the current instance that have a value */ // @TODO: Why the heck can't we just write to this. - public get channels(): ChannelData[] { return this._channels.filter((c) => c.isSet); } + public get channels(): ChannelData[] { return this._channels.filter((c) => c?.isSet); } /** - * Gets the identification used for the current instance + * Gets the identification used to identify the situation and/or device where this adjustment + * should apply. */ public get identification(): string { return this._identification; } @@ -284,11 +316,7 @@ export class RelativeVolumeFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = RelativeVolumeFrame.fromIdentification(this.identification); - for (let i = 0; i < 9; i++) { - frame._channels[i] = this._channels[i]; - } - return frame; + return RelativeVolumeFrame.fromFields(this._identification, this._channels.map(c => c.clone())); } public static filterFrames(frames: Frame[]): RelativeVolumeFrame[] { diff --git a/src/id3v2/frames/synchronizedLyricsFrame.ts b/src/id3v2/frames/synchronizedLyricsFrame.ts index 8d2e5cfb..f699e97f 100644 --- a/src/id3v2/frames/synchronizedLyricsFrame.ts +++ b/src/id3v2/frames/synchronizedLyricsFrame.ts @@ -75,11 +75,11 @@ export class SynchronizedText { */ export class SynchronizedLyricsFrame extends Frame { private _description: string; - private _format: TimestampFormat = TimestampFormat.Unknown; + private _format: TimestampFormat; private _language: string; - private _text: SynchronizedText[] = []; - private _textEncoding: StringType = Id3v2Settings.defaultEncoding; - private _textType: SynchronizedTextType = SynchronizedTextType.Other; + private _text: SynchronizedText[]; + private _textEncoding: StringType; + private _textType: SynchronizedTextType; // #region Constructors @@ -172,22 +172,35 @@ export class SynchronizedLyricsFrame extends Frame { /** * Constructs and initializes a new instance with a specified description, ISO-639-2 language * code, text type, and text encoding. - * @param description Description of the synchronized lyrics frame - * @param language ISO-639-2 language code of the new instance - * @param textType Type of the text to store in the new instance - * @param encoding Encoding to use when rendering text in this new instance + * @param description Optional, description of the synchronized lyrics frame. If omitted, + * defaults to `""` + * @param synchronizedLyrics Optional, synchronized lyrics to store in the lyrics frame. If + * omitted, defaults to an empty array. + * @param language Optional, ISO-639-2 language code of the new instance. If omitted, defaults + * to "XXX". + * @param textType Optional, type of the text to store in the new instance. If omitted, + * defaults to {@link SynchronizedTextType.Other}. + * @param encoding Optional, encoding to use when rendering text in this new instance. If + * omitted, defaults to {@link Id3v2Settings.defaultEncoding}. + * @param timestampFormat Optional, format that the synchronized lyric timestamps are formatted + * in. If omitted, defaults to {@link TimestampFormat.Unknown}. */ - public static fromInfo( - description: string, - language: string, - textType: SynchronizedTextType, - encoding: StringType = Id3v2Settings.defaultEncoding + public static fromFields( + description?: string, + synchronizedLyrics?: SynchronizedText[], + language?: string, + textType?: SynchronizedTextType, + encoding?: StringType, + timestampFormat?: TimestampFormat ): SynchronizedLyricsFrame { const frame = new SynchronizedLyricsFrame(new Id3v2FrameHeader(FrameIdentifiers.SYLT)); - frame.textEncoding = encoding; - frame._language = language; - frame.description = description; - frame.textType = textType; + frame._description = description ?? ""; + frame._format = timestampFormat ?? TimestampFormat.Unknown; + frame._language = language ?? "XXX"; // @TODO: Should this be `unk`? + frame._text = synchronizedLyrics ?? []; + frame._textEncoding = encoding ?? Id3v2Settings.defaultEncoding; + frame._textType = textType ?? SynchronizedTextType.Other; + return frame; } @@ -238,7 +251,7 @@ export class SynchronizedLyricsFrame extends Frame { * Sets the text contained in the current instance * @param value Text contained in the current instance */ - public set text(value: SynchronizedText[]) { this._text = value || []; } + public set text(value: SynchronizedText[]) { this._text = value ?? []; } /** * Gets the text encoding to use when storing the current instance @@ -274,15 +287,13 @@ export class SynchronizedLyricsFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = SynchronizedLyricsFrame.fromInfo( - this.description, + return SynchronizedLyricsFrame.fromFields( + this._description, + this._text.map(i => i.clone()), this._language, - this.textType, - this.textEncoding - ); - frame.format = this.format; - frame._text = this._text.map((i) => i.clone()); - return frame; + this._textType, + this._textEncoding, + this._format); } // #endregion diff --git a/src/id3v2/frames/termsOfUseFrame.ts b/src/id3v2/frames/termsOfUseFrame.ts index c8022046..4b308d2e 100644 --- a/src/id3v2/frames/termsOfUseFrame.ts +++ b/src/id3v2/frames/termsOfUseFrame.ts @@ -47,17 +47,18 @@ export default class TermsOfUseFrame extends Frame { /** * Constructs and initializes a new instance with a specified language. - * @param language ISO-639-2 language code for the new frame - * @param textEncoding Optional, text encoding to use when rendering the new frame. If not - * provided defaults to {@link Id3v2Settings.defaultEncoding} + * @param text Optional, text to store in the new frame. If omitted, defaults to `""`. + * @param language Optional, ISO-639-2 language code for the new frame. If omitted, defaults + * to "XXX". + * @param textEncoding Optional, text encoding to use when rendering the new frame. If omitted, + * defaults to {@link Id3v2Settings.defaultEncoding} */ - public static fromFields( - language: string, - textEncoding: StringType = Id3v2Settings.defaultEncoding - ): TermsOfUseFrame { + public static fromFields(text?: string, language?: string, textEncoding?: StringType): TermsOfUseFrame { const f = new TermsOfUseFrame(new Id3v2FrameHeader(FrameIdentifiers.USER)); - f.textEncoding = textEncoding; - f._language = language; + f._language = language ?? "XXX"; // @TODO: Should this be "unk"? + f._text = text ?? ""; + f._textEncoding = textEncoding ?? Id3v2Settings.defaultEncoding; + return f; } @@ -111,9 +112,7 @@ export default class TermsOfUseFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = TermsOfUseFrame.fromFields(this._language, this.textEncoding); - frame.text = this.text; - return frame; + return TermsOfUseFrame.fromFields(this._text, this._language, this._textEncoding); } /** diff --git a/src/id3v2/frames/textInformationFrame.ts b/src/id3v2/frames/textInformationFrame.ts index b54ca187..c59bb6ba 100644 --- a/src/id3v2/frames/textInformationFrame.ts +++ b/src/id3v2/frames/textInformationFrame.ts @@ -143,21 +143,12 @@ export default class TextInformationFrame extends Frame { FrameIdentifiers.TPE4 ]; - // @TODO: no protected access to members - /** - * Text encoding to use to store the text contents of the current instance. - * @protected - */ - protected _encoding: StringType = Id3v2Settings.defaultEncoding; - /** - * Decoded text contained in the current instance. - * @protected - */ - protected _textFields: string[] = []; + private _encoding: StringType = Id3v2Settings.defaultEncoding; + private _textFields: string[] = []; // #region Constructors - protected constructor(header: Id3v2FrameHeader) { + private constructor(header: Id3v2FrameHeader) { super(header); } @@ -215,15 +206,22 @@ export default class TextInformationFrame extends Frame { /** * Constructs and initializes a new instance with a specified identifier * @param identifier Byte vector containing the identifier for the frame - * @param encoding Optionally, the encoding to use for the new instance. If omitted, defaults - * to {@link Id3v2Settings.defaultEncoding} + * @param text Optional, the text to contain in the frame. If omitted, defaults to an empty + * array. + * @param textEncoding Optionally, the encoding to use for the new instance. If omitted, + * defaults to {@link Id3v2Settings.defaultEncoding} */ - public static fromIdentifier( + public static fromFields( identifier: FrameIdentifier, - encoding: StringType = Id3v2Settings.defaultEncoding + text?: string[], + textEncoding?: StringType ): TextInformationFrame { + Guards.truthy(identifier, "identifier"); + const frame = new TextInformationFrame(new Id3v2FrameHeader(identifier)); - frame._encoding = encoding; + frame._encoding = textEncoding ?? Id3v2Settings.defaultEncoding; + frame._textFields = text ?? []; + return frame; } @@ -233,17 +231,15 @@ export default class TextInformationFrame extends Frame { /** * Gets the text contained in the current instance. - * Note: Modifying the contents of the returned value will not modify the contents of the - * current instance. The value must be reassigned for the value to change. */ public get text(): string[] { - return this._textFields.slice(); + return this._textFields; } /** * Sets the text contained in the current instance. */ public set text(value: string[]) { - this._textFields = value ? value.slice() : []; + this._textFields = value ?? []; } /** @@ -274,9 +270,7 @@ export default class TextInformationFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = TextInformationFrame.fromIdentifier(this.frameId, this._encoding); - frame._textFields = this._textFields.slice(); - return frame; + return TextInformationFrame.fromFields(this.frameId, this._textFields.slice(), this._encoding); } /** diff --git a/src/id3v2/frames/uniqueFileIdentifierFrame.ts b/src/id3v2/frames/uniqueFileIdentifierFrame.ts index 1cd833cf..d1438461 100644 --- a/src/id3v2/frames/uniqueFileIdentifierFrame.ts +++ b/src/id3v2/frames/uniqueFileIdentifierFrame.ts @@ -18,24 +18,6 @@ export default class UniqueFileIdentifierFrame extends Frame { super(header); } - /** - * Constructs and initializes a new instance using the provided information - * @param owner Owner of the new frame. Should be an email or url to the database where this - * unique identifier is applicable - * @param identifier Unique identifier to store in the frame. Must be no more than 64 bytes - */ - public static fromData(owner: string, identifier: ByteVector): UniqueFileIdentifierFrame { - Guards.notNullOrUndefined(owner, "owner"); - if (identifier && identifier.length > 64) { - throw new Error("Argument out of range: Identifier cannot be longer than 64 bytes"); - } - - const frame = new UniqueFileIdentifierFrame(new Id3v2FrameHeader(FrameIdentifiers.UFID)); - frame._owner = owner; - frame._identifier = identifier?.toByteVector(); - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -67,6 +49,25 @@ export default class UniqueFileIdentifierFrame extends Frame { return frame; } + /** + * Constructs and initializes a new instance using the provided information + * @param owner Optional, owner of the identifier. Should be an email or url to the database + * where this unique identifier is applicable. If omitted, defaults to `""`. + * @param identifier Optional, unique identifier to store in the frame. Must be no more than 64 + * bytes. If omitted, defaults to an empty {@link ByteVector}. + */ + public static fromFields(owner?: string, identifier?: ByteVector): UniqueFileIdentifierFrame { + if (identifier && identifier.length > 64) { + throw new Error("Argument out of range: Identifier cannot be longer than 64 bytes"); + } + + const frame = new UniqueFileIdentifierFrame(new Id3v2FrameHeader(FrameIdentifiers.UFID)); + frame._owner = owner ?? ""; + frame._identifier = identifier ?? ByteVector.empty(); + + return frame; + } + // #endregion // #region Properties diff --git a/src/id3v2/frames/unknownFrame.ts b/src/id3v2/frames/unknownFrame.ts index 19f197d9..740d58ed 100644 --- a/src/id3v2/frames/unknownFrame.ts +++ b/src/id3v2/frames/unknownFrame.ts @@ -31,13 +31,15 @@ export default class UnknownFrame extends Frame { /** * Constructs and initializes a new instance with a specified type * @param identifier ID3v2 frame identifier - * @param data Contents of the frame + * @param data Optional, contents of the frame. If omitted, defaults to an empty + * {@link ByteVector}. */ - public static fromData(identifier: FrameIdentifier, data?: ByteVector): UnknownFrame { + public static fromFields(identifier: FrameIdentifier, data?: ByteVector): UnknownFrame { Guards.truthy(identifier, "identifier"); const frame = new UnknownFrame(new Id3v2FrameHeader(identifier)); - frame.data = data?.toByteVector(); + + frame.data = data ?? ByteVector.empty(); return frame; } @@ -53,7 +55,7 @@ export default class UnknownFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - return UnknownFrame.fromData(this.header.frameId, this.data); + return UnknownFrame.fromFields(this.frameId, this.data.toByteVector()); } /** @inheritDoc */ diff --git a/src/id3v2/frames/unsynchronizedLyricsFrame.ts b/src/id3v2/frames/unsynchronizedLyricsFrame.ts index 628cce7f..584cc545 100644 --- a/src/id3v2/frames/unsynchronizedLyricsFrame.ts +++ b/src/id3v2/frames/unsynchronizedLyricsFrame.ts @@ -21,24 +21,6 @@ export default class UnsynchronizedLyricsFrame extends Frame { super(header); } - /** - * Constructs and initializes a new instance from the provided data - * @param description Description of the frame - * @param language ISO-639-2 language code for the content of the frame - * @param encoding Encoding to use when storing the content of the frame - */ - public static fromData( - description: string, - language?: string, - encoding: StringType = Id3v2Settings.defaultEncoding - ): UnsynchronizedLyricsFrame { - const frame = new UnsynchronizedLyricsFrame(new Id3v2FrameHeader(FrameIdentifiers.USLT)); - frame._textEncoding = encoding; - frame._language = language; - frame._description = description; - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -86,6 +68,30 @@ export default class UnsynchronizedLyricsFrame extends Frame { return frame; } + /** + * Constructs and initializes a new instance from the provided data + * @param description Optional, description of the frame. If omitted, defaults to `""`. + * @param text Optional, text to store as the lyrics. If omitted, defaults to `""`. + * @param language Optional, ISO-639-2 language code for the content of the frame. If omitted, + * defaults to "XXX". + * @param encoding Optional, encoding to use when storing the content of the frame. If omitted, + * defaults to {@link Id3v2Settings.defaultEncoding}. + */ + public static fromFields( + description?: string, + text?: string, + language?: string, + encoding?: StringType + ): UnsynchronizedLyricsFrame { + const frame = new UnsynchronizedLyricsFrame(new Id3v2FrameHeader(FrameIdentifiers.USLT)); + frame._description = description ?? ""; + frame._language = language ?? "XXX"; + frame._text = text ?? ""; + frame._textEncoding = encoding ?? Id3v2Settings.defaultEncoding; + + return frame; + } + // #endregion // #region Properties @@ -139,9 +145,12 @@ export default class UnsynchronizedLyricsFrame extends Frame { /** @inheritDoc */ public clone(): Frame { - const frame = UnsynchronizedLyricsFrame.fromData(this._description, this._language, this.textEncoding); - frame._text = this._text; - return frame; + return UnsynchronizedLyricsFrame.fromFields( + this._description, + this._text, + this._language, + this._textEncoding + ); } /** diff --git a/src/id3v2/frames/urlLinkFrame.ts b/src/id3v2/frames/urlLinkFrame.ts index 57f7d677..df1a528a 100644 --- a/src/id3v2/frames/urlLinkFrame.ts +++ b/src/id3v2/frames/urlLinkFrame.ts @@ -32,16 +32,11 @@ import {ArrayUtils, Guards} from "../../utils"; * for the publisher. */ export default class UrlLinkFrame extends Frame { - // @TODO: Don't allow protected member variables - /** - * Decoded text contained in the current instance. - * @protected - */ - protected _text: string; + private _url: string; // #region Constructors - protected constructor(header: Id3v2FrameHeader) { + private constructor(header: Id3v2FrameHeader) { super(header); } @@ -60,18 +55,23 @@ export default class UrlLinkFrame extends Frame { // If data contains a string terminator, ignore everything after it. const splitData = fieldBytes.split(ByteVector.getTextDelimiter(StringType.Latin1)); - frame._text = splitData[0].toString(StringType.Latin1); + frame._url = splitData[0].toString(StringType.Latin1); return frame; } /** * Constructs and initializes an empty frame with the provided frame identity - * @param ident Identity of the frame to construct + * @param ident Identity of the frame to construct. Required. + * @param text Optional, text to store as the url for the frame. If omitted, defaults to `""`. */ - public static fromIdentifier(ident: FrameIdentifier): UrlLinkFrame { + public static fromFields(ident: FrameIdentifier, text?: string): UrlLinkFrame { Guards.truthy(ident, "ident"); - return new UrlLinkFrame(new Id3v2FrameHeader(ident)); + + const frame = new UrlLinkFrame(new Id3v2FrameHeader(ident)); + frame._url = text ?? ""; + + return frame; } // #endregion @@ -79,13 +79,13 @@ export default class UrlLinkFrame extends Frame { // #region Properties /** - * Gets the text contained in the current instance. + * Gets the url contained in the current instance. */ - public get text(): string { return this._text; } + public get url(): string { return this._url; } /** - * Sets the text contained in the current instance. + * Sets the url contained in the current instance. */ - public set text(value: string) { this._text = value; } + public set url(value: string) { this._url = value; } // #endregion @@ -101,23 +101,21 @@ export default class UrlLinkFrame extends Frame { /** @inheritDoc */ public clone(): UrlLinkFrame { - const frame = UrlLinkFrame.fromIdentifier(this.frameId); - frame._text = this._text; - return frame; + return UrlLinkFrame.fromFields(this.frameId, this._url); } /** @inheritDoc */ public toString(): string { - return this.text; + return this.url; } /** @inheritDoc */ protected renderFields(_version: number): ByteVector { - if (!this._text) { + if (!this._url) { return ByteVector.empty(); } - return ByteVector.fromString(this.text, StringType.Latin1); + return ByteVector.fromString(this.url, StringType.Latin1); } // #endregion diff --git a/src/id3v2/frames/userTextInformationFrame.ts b/src/id3v2/frames/userTextInformationFrame.ts index f69d275d..4eabddac 100644 --- a/src/id3v2/frames/userTextInformationFrame.ts +++ b/src/id3v2/frames/userTextInformationFrame.ts @@ -1,14 +1,15 @@ import Frame from "./frame"; -import TextInformationFrame from "./textInformationFrame"; import Id3v2Settings from "../id3v2Settings"; import {ByteVector, StringType} from "../../byteVector"; +import {CorruptFileError} from "../../errors"; import {Id3v2FrameHeader} from "./frameHeader"; import {FrameIdentifiers} from "../frameIdentifiers"; import {ArrayUtils, Guards} from "../../utils"; -import {CorruptFileError} from "../../errors"; -export default class UserTextInformationFrame extends TextInformationFrame { +export default class UserTextInformationFrame extends Frame { private _description: string; + private _encoding: StringType; + private _textFields: string[]; // #region Constructors @@ -16,21 +17,6 @@ export default class UserTextInformationFrame extends TextInformationFrame { super(header); } - /** - * Constructs and initializes a new instance with a specified description and text encoding. - * @param description Description of the new frame - * @param encoding Text encoding to use when rendering the new frame - */ - public static fromDescription( - description: string, - encoding: StringType = Id3v2Settings.defaultEncoding - ): UserTextInformationFrame { - const frame = new UserTextInformationFrame(new Id3v2FrameHeader(FrameIdentifiers.TXXX)); - frame._encoding = encoding; - frame._description = description; - return frame; - } - /** * Constructs and initializes a new instance by parsing the fields from the field bytes. * @param header Header of the frame @@ -73,6 +59,24 @@ export default class UserTextInformationFrame extends TextInformationFrame { return frame; } + /** + * Constructs and initializes a new instance with a specified description, text fields, and + * text encoding. + * @param description Optional, description of the new frame. If omitted, defaults to `""`. + * @param text Optional, text fields to store in the new frame. If omitted, defaults to an + * empty array. + * @param encoding Optional, text encoding to use when rendering the new frame. If omitted, + * defaults to {@link Id3v2Settings.defaultEncoding}. + */ + public static fromFields(description?: string, text?: string[], encoding?: StringType + ): UserTextInformationFrame { + const frame = new UserTextInformationFrame(new Id3v2FrameHeader(FrameIdentifiers.TXXX)); + frame._encoding = encoding ?? Id3v2Settings.defaultEncoding; + frame._description = description ?? ""; + frame._textFields = text ?? []; + return frame; + } + // #endregion // #region Properties @@ -90,15 +94,23 @@ export default class UserTextInformationFrame extends TextInformationFrame { /** * Gets the text contained in the current instance. - * NOTE: Modifying the contents of the returned value will not modify the contents of the - * current instance. The value must be reassigned for the value to change. */ - public get text(): string[] { return this._textFields.slice(); } + public get text(): string[] { return this._textFields; } /** * Sets the text contained in the current instance. * @param value Array of text values to store in the current instance */ - public set text(value: string[]) { this._textFields = value ? value.slice() : []; } + public set text(value: string[]) { this._textFields = value ?? []; } + + /** + * Gets the text encoding to use when rendering the current instance. + */ + public get textEncoding(): StringType { return this._encoding; } + /** + * Sets the text encoding to use when rendering the current instance. + * This value will be overridden if {@link Id3v2Settings.forceDefaultEncoding} is `true`. + */ + public set textEncoding(value: StringType) { this._encoding = value; } // #endregion @@ -111,14 +123,12 @@ export default class UserTextInformationFrame extends TextInformationFrame { /** @inheritDoc */ public clone(): Frame { - const frame = UserTextInformationFrame.fromDescription(this._description, this._encoding); - frame._textFields = this._textFields.slice(); - return frame; + return UserTextInformationFrame.fromFields(this._description, this._textFields.slice(), this._encoding); } /** @inheritDoc */ public toString(): string { - return `[${this.description}] ${super.toString()}`; + return `[${this._description}] ${this._textFields.join("; ")}`; } /** @inheritDoc */ @@ -127,23 +137,25 @@ export default class UserTextInformationFrame extends TextInformationFrame { return ByteVector.empty(); } - const encoding = TextInformationFrame.correctEncoding(this._encoding, version); - const v = ByteVector.empty(); - v.addByte(encoding); - v.addByteVector(ByteVector.fromString(this._description ?? "", encoding)); - - for (const text of this._textFields) { - v.addByteVector(ByteVector.getTextDelimiter(encoding)); - if (text) { - v.addByteVector(ByteVector.fromString(text, encoding)); - } - } - - if (this._textFields.length === 0) { - v.addByteVector(ByteVector.getTextDelimiter(encoding)); - } - - return v; + // Convert ["x", "y", "z"] into [bv("x"), bv(0), bv("y"), bv(0), bv("z"), bv(0)] + const encoding = Frame.correctEncoding(this._encoding, version); + const renderedFields = this._textFields.filter(f => f !== undefined && f !== null) + .map(f => [ByteVector.fromString(f, encoding), ByteVector.getTextDelimiter(encoding)]) + .reduce( + (flattened, nested) => { + flattened.push(... nested); + return flattened; + }, + [] + ); + // @TODO: Update to use .flat + + return ByteVector.concatenate( + encoding, + ByteVector.fromString(this._description ?? "", encoding), + ByteVector.getTextDelimiter(encoding), + ... renderedFields.slice(0, renderedFields.length - 1) // Drop last delimiter + ) } // #endregion diff --git a/src/id3v2/frames/userUrlLinkFrame.ts b/src/id3v2/frames/userUrlLinkFrame.ts index 004bda5b..1c362b09 100644 --- a/src/id3v2/frames/userUrlLinkFrame.ts +++ b/src/id3v2/frames/userUrlLinkFrame.ts @@ -1,6 +1,5 @@ import Frame from "./frame"; import Id3v2Settings from "../id3v2Settings"; -import UrlLinkFrame from "./urlLinkFrame"; import {ByteVector, StringType} from "../../byteVector"; import {Id3v2FrameHeader} from "./frameHeader"; import {FrameIdentifiers} from "../frameIdentifiers"; @@ -9,9 +8,10 @@ import {ArrayUtils, Guards} from "../../utils"; /** * Provides support for ID3v2 User URL Link frames (WXXX). */ -export default class UserUrlLinkFrame extends UrlLinkFrame { +export default class UserUrlLinkFrame extends Frame { private _description: string; private _encoding: StringType = Id3v2Settings.defaultEncoding; + private _url: string; // #region Constructors @@ -54,11 +54,11 @@ export default class UserUrlLinkFrame extends UrlLinkFrame { if (splitText.length > 1) { // Data was probably encoded using old TagLib# behavior. frame._description = splitText[0]; - frame._text = splitText[1]; + frame._url = splitText[1]; } else { // Data has only one field, let's assume it only has a url. frame._description = ""; - frame._text = splitText[0]; + frame._url = splitText[0]; } } else { // Well-formed frame (or >2 fields, the latter of which will be ignored) @@ -67,7 +67,7 @@ export default class UserUrlLinkFrame extends UrlLinkFrame { const textBytes = descriptionAndTextBytes.subarray(descriptionLength + delimiter.length); const splitTextBytes = textBytes.split(ByteVector.getTextDelimiter(StringType.Latin1)); - frame._text = splitTextBytes[0].toString(StringType.Latin1); + frame._url = splitTextBytes[0].toString(StringType.Latin1); } return frame; @@ -76,13 +76,13 @@ export default class UserUrlLinkFrame extends UrlLinkFrame { /** * Constructs and initializes a new instance using the provided description and url to populate * the fields of the frame. - * @param description Description to store in the frame - * @param url URL to store in the frame + * @param description Optional, description to store in the frame. If omitted, defaults to `""`. + * @param url Optional, URL to store in the frame. If omitted, defaults to `""`. */ - public static fromFields(description: string, url: string): UserUrlLinkFrame { + public static fromFields(description?: string, url?: string): UserUrlLinkFrame { const frame = new UserUrlLinkFrame(new Id3v2FrameHeader(FrameIdentifiers.WXXX)); - frame._description = description; - frame._text = url; + frame._description = description ?? ""; + frame._url = url ?? ""; return frame; } @@ -111,6 +111,15 @@ export default class UserUrlLinkFrame extends UrlLinkFrame { */ public set textEncoding(value: StringType) { this._encoding = value; } + /** + * Gets the text contained in the current instance. + */ + public get url(): string { return this._url; } + /** + * Sets the text contained in the current instance. + */ + public set url(value: string) { this._url = value; } + // #endregion // #region Methods @@ -122,27 +131,27 @@ export default class UserUrlLinkFrame extends UrlLinkFrame { /** @inheritDoc */ public clone(): UserUrlLinkFrame { - const frame = UserUrlLinkFrame.fromFields(this._description, this._text); + const frame = UserUrlLinkFrame.fromFields(this._description, this._url); frame._encoding = this._encoding; return frame; } /** @inheritDoc */ public toString(): string { - return `[${this.description}] ${super.toString()}`; + return `[${this._description}] ${this._url}`; } protected renderFields(version: number): ByteVector { - if (!this._description && !this._text) { + if (!this._description && !this._url) { return ByteVector.empty(); } - const encoding = UrlLinkFrame.correctEncoding(this.textEncoding, version); + const encoding = Frame.correctEncoding(this._encoding, version); return ByteVector.concatenate( - UrlLinkFrame.correctEncoding(this._encoding, version), + encoding, ByteVector.fromString(this._description ?? "", encoding), ByteVector.getTextDelimiter(encoding), - ByteVector.fromString(this._text ?? "", StringType.Latin1) + ByteVector.fromString(this._url ?? "", StringType.Latin1) ); } diff --git a/src/id3v2/id3v2Tag.ts b/src/id3v2/id3v2Tag.ts index 2a14e96d..5a8217d1 100644 --- a/src/id3v2/id3v2Tag.ts +++ b/src/id3v2/id3v2Tag.ts @@ -45,7 +45,7 @@ export default class Id3v2Tag extends Tag { */ public static fromEmpty(): Id3v2Tag { const tag = new Id3v2Tag(); - tag._header = new Id3v2TagHeader(); + tag._header = new Id3v2TagHeader(0, 0, Id3v2TagHeaderFlags.None, 0); return tag; } @@ -471,7 +471,7 @@ export default class Id3v2Tag extends Tag { // Create or update the preferred comments frame let frame = this.getCommentFramePreferred("", Id3v2Tag.language); if (!frame) { - frame = CommentsFrame.fromDescription("", Id3v2Tag.language); + frame = CommentsFrame.fromFields("", undefined, Id3v2Tag.language); this.addFrame(frame); } @@ -609,9 +609,7 @@ export default class Id3v2Tag extends Tag { * @inheritDoc * @remarks Stored in the `USLT` frame */ - get lyrics(): string { - return this.getLyricsFramePreferred("", Id3v2Tag.language)?.toString(); - } + get lyrics(): string { return this.getLyricsFramePreferred("", Id3v2Tag.language)?.toString(); } /** * @inheritDoc * @remarks Stored in the `USLT` frame @@ -626,7 +624,7 @@ export default class Id3v2Tag extends Tag { // Find or create the appropriate unsynchronized lyrics frame let frame = this.getLyricsFramePreferred("", Id3v2Tag.language); if (!frame) { - frame = UnsynchronizedLyricsFrame.fromData("", Id3v2Tag.language); + frame = UnsynchronizedLyricsFrame.fromFields("", undefined, Id3v2Tag.language); this.addFrame(frame); } frame.text = value; @@ -1042,28 +1040,6 @@ export default class Id3v2Tag extends Tag { } } - /** - * Gets the text value from a specified text information frame (or URL frame if that was - * specified). - * @param ident Frame identifier of the text information frame to get the value from - * @returns Text of the specified frame, or `undefined` if no value was found - */ - public getTextAsString(ident: FrameIdentifier): string { - Guards.truthy(ident, "ident"); - - let frame: Frame; - if (ident.isUrlFrame) { - frame = UrlLinkFrame.filterFrames(this._frameList, ident)[0]; - } else if (ident === FrameIdentifiers.TCON) { - frame = GenreFrame.filterFrames(this._frameList)[0]; - } else { - frame = TextInformationFrame.filterFrames(this._frameList, ident)[0]; - } - - const result = frame ? frame.toString() : undefined; - return result || undefined; - } - /** * Removes a specified frame from the current instance. * @param frame Object to remove from the current instance @@ -1174,6 +1150,8 @@ export default class Id3v2Tag extends Tag { } }); + // @TODO: Determine what to do with empty frames, maybe even have a setting to enable writing them + // Put the tag data together and unsynchronize it. let frameBytes = ByteVector.concatenate(... renderedFrames); if (unsyncAtTagLevel) { @@ -1231,107 +1209,6 @@ export default class Id3v2Tag extends Tag { } } - /** - * Sets the numerical values for a specified text information frame. - * If both `numerator` and `denominator` are `0`, the frame will be removed - * from the tag. If `denominator` is zero, `numerator` will be stored by - * itself. Otherwise, the values will be stored as `{numerator}/{denominator}`. - * @param ident Identity of the frame to set - * @param numerator Value containing the top half of the fraction, or the number if - * `denominator` is zero - * @param denominator Value containing the bottom half of the fraction - * @param minPlaces Minimum number of digits to use to display the `numerator`, if - * the numerator has less than this number of digits, it will be filled with leading zeroes. - */ - public setNumberFrame(ident: FrameIdentifier, numerator: number, denominator: number, minPlaces: number = 1): void { - Guards.truthy(ident, "ident"); - Guards.uint(numerator, "value"); - Guards.uint(denominator, "count"); - Guards.byte(minPlaces, "minPlaces"); - - if (numerator === 0 && denominator === 0) { - this.removeFrames(ident); - } else if (denominator !== 0) { - const formattedNumerator = numerator.toString().padStart(minPlaces, "0"); - this.setTextFrame(ident, `${formattedNumerator}/${denominator}`); - } else { - this.setTextFrame(ident, numerator.toString().padStart(minPlaces, "0")); - } - } - - /** - * Sets the text for a specified text information frame. - * @param ident Identifier of the frame to set the data for - * @param text Text to set for the specified frame or `undefined`/`null`/`""` to remove all - * frames with that identifier. - */ - // @TODO: These methods don't cover all situations - what happens if there is >1 frame with the identity? - // what happens if the user specifies a TXXX frame? - public setTextFrame(ident: FrameIdentifier, ...text: string[]): void { - Guards.truthy(ident, "ident"); - if (!ident.isTextFrame) { - throw new Error("Argument error: Identifier is not a text frame."); - } - if (ident === FrameIdentifiers.TXXX) { - throw new Error("Argument error: TXXX frames cannot be set using this method."); - } - - // Check if all the elements provided are empty. If they are, remove the frame. - if (!text.some(t => !!t)) { - this.removeFrames(ident); - return; - } - - let frame: TextInformationFrame|GenreFrame; - if (ident === FrameIdentifiers.TCON) { - frame = GenreFrame.filterFrames(this._frameList)[0]; - if (!frame) { - frame = GenreFrame.fromEncoding(); - this.addFrame(frame); - } - } else { - frame = TextInformationFrame.filterFrames(this._frameList, ident)[0]; - if (!frame) { - frame = TextInformationFrame.fromIdentifier(ident); - this.addFrame(frame); - } - } - - frame.text = text; - frame.textEncoding = Id3v2Settings.defaultEncoding; - } - - /** - * Sets the text for a specified URL frame. - * @param ident Identifier of the frame to set the data for - * @param text URL to set for the specified frame or `undefined`/`null`/`""` to remove all - * frames with that identifier. - */ - // @TODO: These methods don't cover all situations - what happens if there is >1 frame with the identity? - // what happens if the user specifies a WXXX frame? - public setUrlFrame(ident: FrameIdentifier, text: string): void { - Guards.truthy(ident, "ident"); - if (!ident.isUrlFrame) { - throw new Error("Argument error: Identifier is not a URL frame."); - } - if (ident === FrameIdentifiers.WXXX) { - throw new Error("Argument error: WXXX frames cannot be set using this method."); - } - - if (!text) { - this.removeFrames(ident); - return; - } - - let urlFrame = UrlLinkFrame.filterFrames(this._frameList, ident)[0]; - if (!urlFrame) { - urlFrame = UrlLinkFrame.fromIdentifier(ident); - this.addFrame(urlFrame); - } - - urlFrame.text = text; - } - // #endregion // #region Protected/Private Methods @@ -1566,6 +1443,66 @@ export default class Id3v2Tag extends Tag { return result || undefined; } + private getUserTextFrame(description: string, caseSensitive: boolean): UserTextInformationFrame { + // Gets the TXXX frame, frame will be undefined if nonexistent + const frames = UserTextInformationFrame.filterFrames(this._frameList); + return frames.find(f => { + return caseSensitive + ? f.description === description + : f.description.toUpperCase() === description.toUpperCase(); + }); + } + + /** + * Gets the text value from a specified text information frame (or URL frame if that was + * specified). + * @param ident Frame identifier of the text information frame to get the value from + * @returns Text of the specified frame, or `undefined` if no value was found + */ + private getTextAsString(ident: FrameIdentifier): string { + Guards.truthy(ident, "ident"); + + let frame: Frame; + if (ident.isUrlFrame) { + frame = UrlLinkFrame.filterFrames(this._frameList, ident)[0]; + } else if (ident === FrameIdentifiers.TCON) { + frame = GenreFrame.filterFrames(this._frameList)[0]; + } else { + frame = TextInformationFrame.filterFrames(this._frameList, ident)[0]; + } + + const result = frame ? frame.toString() : undefined; + return result || undefined; + } + + /** + * Sets the numerical values for a specified text information frame. + * If both `numerator` and `denominator` are `0`, the frame will be removed + * from the tag. If `denominator` is zero, `numerator` will be stored by + * itself. Otherwise, the values will be stored as `{numerator}/{denominator}`. + * @param ident Identity of the frame to set + * @param numerator Value containing the top half of the fraction, or the number if + * `denominator` is zero + * @param denominator Value containing the bottom half of the fraction + * @param minPlaces Minimum number of digits to use to display the `numerator`, if + * the numerator has less than this number of digits, it will be filled with leading zeroes. + */ + private setNumberFrame(ident: FrameIdentifier, numerator: number, denominator: number, minPlaces: number = 1): void { + Guards.truthy(ident, "ident"); + Guards.uint(numerator, "value"); + Guards.uint(denominator, "count"); + Guards.byte(minPlaces, "minPlaces"); + + if (numerator === 0 && denominator === 0) { + this.removeFrames(ident); + } else if (denominator !== 0) { + const formattedNumerator = numerator.toString().padStart(minPlaces, "0"); + this.setTextFrame(ident, `${formattedNumerator}/${denominator}`); + } else { + this.setTextFrame(ident, numerator.toString().padStart(minPlaces, "0")); + } + } + private setUfidText(owner: string, text: string): void { // Get the UFID frame, create if necessary const frames = UniqueFileIdentifierFrame.filterFrames(this._frameList); @@ -1577,7 +1514,7 @@ export default class Id3v2Tag extends Tag { this.removeFrame(frame); } else { const identifier = ByteVector.fromString(text, StringType.UTF8); - frame = UniqueFileIdentifierFrame.fromData(owner, identifier); + frame = UniqueFileIdentifierFrame.fromFields(owner, identifier); this.addFrame(frame); } } @@ -1591,21 +1528,45 @@ export default class Id3v2Tag extends Tag { } } else { if (!frame) { - frame = UserTextInformationFrame.fromDescription(description, Id3v2Settings.defaultEncoding); + frame = UserTextInformationFrame.fromFields(description); this.addFrame(frame); } frame.text = text.split(";"); } } - private getUserTextFrame(description: string, caseSensitive: boolean): UserTextInformationFrame { - // Gets the TXXX frame, frame will be undefined if nonexistent - const frames = UserTextInformationFrame.filterFrames(this._frameList); - return frames.find(f => { - return caseSensitive - ? f.description === description - : f.description.toUpperCase() === description.toUpperCase(); - }); + /** + * Sets the text for a specified text information frame. + * @param ident Identifier of the frame to set the data for + * @param text Text to set for the specified frame or `undefined`/`null`/`""` to remove all + * frames with that identifier. + */ + // @TODO: These methods don't cover all situations - what happens if there is >1 frame with the identity? + // what happens if the user specifies a TXXX frame? + private setTextFrame(ident: FrameIdentifier, ...text: string[]): void { + // Check if all the elements provided are empty. If they are, remove the frame. + if (!text.some(t => !!t)) { + this.removeFrames(ident); + return; + } + + let frame: TextInformationFrame|GenreFrame; + if (ident === FrameIdentifiers.TCON) { + frame = GenreFrame.filterFrames(this._frameList)[0]; + if (!frame) { + frame = GenreFrame.fromFields(); + this.addFrame(frame); + } + } else { + frame = TextInformationFrame.filterFrames(this._frameList, ident)[0]; + if (!frame) { + frame = TextInformationFrame.fromFields(ident); + this.addFrame(frame); + } + } + + frame.text = text; + frame.textEncoding = Id3v2Settings.defaultEncoding; } // #endregion diff --git a/src/id3v2/id3v2TagHeader.ts b/src/id3v2/id3v2TagHeader.ts index 8ef89112..3039fb62 100644 --- a/src/id3v2/id3v2TagHeader.ts +++ b/src/id3v2/id3v2TagHeader.ts @@ -50,6 +50,24 @@ export class Id3v2TagHeader { private _revisionNumber: number = 0; private _tagSize: number = 0; + // #region Constructors + + /** + * Constructs and initializes a new instance by storing the fields. + * @param majorVersion Major ID3v2 version (ie, 2, 3, or 4). See {@link majorVersion}. + * @param revisionVersion Revision of ID2v2.whatever. See {@link revisionVersion}. + * @param flags Tag flags. See {@link flags}. + * @param tagSize Size of the tag in bytes as it currently exists on the disk. See {@link tagSize}. + * @internal + */ + public constructor(majorVersion: number, revisionVersion: number, flags: Id3v2TagHeaderFlags, tagSize: number) { + this._majorVersion = majorVersion; + + this.flags = flags; + this.revisionNumber = revisionVersion; + this.tagSize = tagSize; + } + /** * Constructs and initializes a new instance by reading it from the raw header data. * @param data Object containing the raw data to build the new instance from. @@ -63,19 +81,18 @@ export class Id3v2TagHeader { throw new CorruptFileError("Provided data does not start with the file identifier"); } - const header = new Id3v2TagHeader(); - header._majorVersion = data.get(3); - header._revisionNumber = data.get(4); - header._flags = data.get(5); + const majorVersion = data.get(3); + const revisionNumber = data.get(4); + const flags = data.get(5); // Make sure flags provided are legal - if (header._majorVersion === 2 && NumberUtils.hasFlag(header._flags, 63)) { + if (majorVersion === 2 && NumberUtils.hasFlag(flags, 63)) { throw new CorruptFileError("Invalid flags set on version 2 tag"); } - if (header._majorVersion === 3 && NumberUtils.hasFlag(header._flags, 15)) { + if (majorVersion === 3 && NumberUtils.hasFlag(flags, 15)) { throw new CorruptFileError("Invalid flags set on version 3 tag"); } - if (header._majorVersion === 4 && NumberUtils.hasFlag(header._flags, 7)) { + if (majorVersion === 4 && NumberUtils.hasFlag(flags, 7)) { throw new CorruptFileError("Invalid flags set on version 4 tag"); } @@ -85,11 +102,13 @@ export class Id3v2TagHeader { throw new CorruptFileError("One of the bytes in the tag size was greater than the allowed 128"); } } - header.tagSize = SyncData.toUint(data.subarray(6, 4)); + const tagSize = SyncData.toUint(data.subarray(6, 4)); - return header; + return new Id3v2TagHeader(majorVersion, revisionNumber, flags, tagSize); } + // #endregion + // #region Properties /** diff --git a/src/utils.ts b/src/utils.ts index b3014227..7ed01be4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -144,6 +144,11 @@ export class Guards { throw new Error(`Argument out of range: ${name} must be a positive, 64-bit integer`); } } + + public static ulongOptional(value: bigint|undefined, name: string): void { + if (value === undefined) { return; } + Guards.ulong(value, name); + } } export class StringComparison { diff --git a/test-integration/mp3_id3v24_fileTests.ts b/test-integration/mp3_id3v24_fileTests.ts index bb697d9f..e7d502cd 100644 --- a/test-integration/mp3_id3v24_fileTests.ts +++ b/test-integration/mp3_id3v24_fileTests.ts @@ -2,8 +2,9 @@ import * as Chai from "chai"; import {suite, test} from "@testdeck/mocha"; import TestConstants from "./utilities/testConstants"; -import {File, Id3v2FrameIdentifiers, Id3v2Tag, ReadStyle, TagTypes} from "../src"; +import {File, Id3v2FrameIdentifiers, Id3v2Tag, Id3v2UrlLinkFrame, ReadStyle, TagTypes} from "../src"; import {StandardFileTests, TestTagLevel} from "./utilities/standardFileTests"; +import UrlLinkFrame from "../src/id3v2/frames/urlLinkFrame"; // Setup chai const assert = Chai.assert; @@ -128,14 +129,22 @@ const assert = Chai.assert; let urlLinkFile = File.createFromPath(tempFilePath); try { const id3v2Tag1 = urlLinkFile.getTag(TagTypes.Id3v2, false); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WCOM, "www.commercial.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WCOP, "www.copyright.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAF, "www.official-audio.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAR, "www.official-artist.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAS, "www.official-audio-source.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WORS, "www.official-internet-radio.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WPAY, "www.payment.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WPUB, "www.official-publisher.com"); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WCOM, "www.commercial.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WCOP, "www.copyright.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAF, "www.official-audio.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAR, "www.official-artist.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAS, "www.official-audio-source.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WORS, "www.official-internet-radio.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WPAY, "www.payment.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WPUB, "www.official-publisher.com")); urlLinkFile.save(); } finally { urlLinkFile.dispose(); @@ -145,35 +154,35 @@ const assert = Chai.assert; try { const id3v2Tag2 = urlLinkFile.getTag(TagTypes.Id3v2, false); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WCOM), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WCOM)[0]?.url, "www.commercial.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WCOP), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WCOP)[0]?.url, "www.copyright.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAF), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAF)[0]?.url, "www.official-audio.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAR), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAR)[0]?.url, "www.official-artist.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAS), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAS)[0]?.url, "www.official-audio-source.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WORS), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WORS)[0]?.url, "www.official-internet-radio.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WPAY), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WPAY)[0]?.url, "www.payment.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WPUB), + UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WPUB)[0]?.url, "www.official-publisher.com" ); } finally { diff --git a/test-integration/mp3_id3v2_fileTests.ts b/test-integration/mp3_id3v2_fileTests.ts index 29c80bcb..f8904f16 100644 --- a/test-integration/mp3_id3v2_fileTests.ts +++ b/test-integration/mp3_id3v2_fileTests.ts @@ -5,7 +5,7 @@ import {suite, test} from "@testdeck/mocha"; import ExtendedFileTests from "./utilities/extendedFileTests"; import TestConstants from "./utilities/testConstants"; import Utilities from "./utilities/utilities"; -import {File, Id3v2FrameIdentifiers, Id3v2Tag, TagTypes} from "../src"; +import {File, Id3v2FrameIdentifiers, Id3v2Tag, Id3v2UrlLinkFrame, TagTypes} from "../src"; import {StandardFileTests} from "./utilities/standardFileTests"; // Setup chai @@ -102,14 +102,22 @@ const assert = Chai.assert; const urlLinkFile1 = File.createFromPath(tempFilePath); try { const id3v2Tag1 = urlLinkFile1.getTag(TagTypes.Id3v2, false); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WCOM, "www.commercial.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WCOP, "www.copyright.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAF, "www.official-audio.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAR, "www.official-artist.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WOAS, "www.official-audio-source.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WORS, "www.official-internet-radio.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WPAY, "www.payment.com"); - id3v2Tag1.setUrlFrame(Id3v2FrameIdentifiers.WPUB, "www.official-publisher.com"); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WCOM, "www.commercial.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WCOP, "www.copyright.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAF, "www.official-audio.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAR, "www.official-artist.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WOAS, "www.official-audio-source.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WORS, "www.official-internet-radio.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WPAY, "www.payment.com")); + id3v2Tag1.addFrame( + Id3v2UrlLinkFrame.fromFields(Id3v2FrameIdentifiers.WPUB, "www.official-publisher.com")); urlLinkFile1.save(); } finally { urlLinkFile1.dispose(); @@ -119,35 +127,35 @@ const assert = Chai.assert; try { const id3v2Tag2 = urlLinkFile1.getTag(TagTypes.Id3v2, false); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WCOM), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WCOM)[0]?.url, "www.commercial.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WCOP), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WCOP)[0]?.url, "www.copyright.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAF), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAF)[0]?.url, "www.official-audio.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAR), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAR)[0]?.url, "www.official-artist.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WOAS), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WOAS)[0]?.url, "www.official-audio-source.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WORS), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WORS)[0]?.url, "www.official-internet-radio.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WPAY), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WPAY)[0]?.url, "www.payment.com" ); assert.strictEqual( - id3v2Tag2.getTextAsString(Id3v2FrameIdentifiers.WPUB), + Id3v2UrlLinkFrame.filterFrames(id3v2Tag2.frames, Id3v2FrameIdentifiers.WPUB)[0]?.url, "www.official-publisher.com" ); } finally { diff --git a/test-unit/id3v2/attachmentsFrameTests.ts b/test-unit/id3v2/attachmentsFrameTests.ts index 9d023f4b..0e341022 100644 --- a/test-unit/id3v2/attachmentsFrameTests.ts +++ b/test-unit/id3v2/attachmentsFrameTests.ts @@ -512,8 +512,8 @@ const verifyFrame = ( @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -527,7 +527,7 @@ const verifyFrame = ( @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const pic2 = Picture.fromData(ByteVector.fromUint(8888)); const frame2 = AttachmentFrame.fromPicture(pic2); @@ -545,7 +545,7 @@ const verifyFrame = ( @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const pic2 = Picture.fromData(ByteVector.fromUint(8888)); const frame2 = AttachmentFrame.fromPicture(pic2); diff --git a/test-unit/id3v2/commentsFrameTests.ts b/test-unit/id3v2/commentsFrameTests.ts index 620dccbb..b05be20a 100644 --- a/test-unit/id3v2/commentsFrameTests.ts +++ b/test-unit/id3v2/commentsFrameTests.ts @@ -12,19 +12,6 @@ import {FrameIdentifiers} from "../../src/id3v2/frameIdentifiers"; import {Id3v2FrameFlags, Id3v2FrameHeader} from "../../src/id3v2/frames/frameHeader"; import {Testers} from "../utilities/testers"; -const getTestFrame = (): CommentsFrame => { - const fieldBytes = ByteVector.concatenate( - StringType.Latin1, - ByteVector.fromString("eng", StringType.Latin1), - ByteVector.fromString("foo", StringType.Latin1), - ByteVector.getTextDelimiter(StringType.Latin1), - ByteVector.fromString("bar", StringType.Latin1) - ); - const header = new Id3v2FrameHeader(FrameIdentifiers.COMM); - - return CommentsFrame.fromFieldBytes(header, fieldBytes, 4); -} - const verifyFrame = ( frame: CommentsFrame, expectedDesc: string, @@ -47,45 +34,6 @@ const verifyFrame = ( return CommentsFrame.fromFieldBytes; } - @test - public fromDescription_withoutLanguage() { - // Arrange - const description = "fux"; - - // Act - const frame = CommentsFrame.fromDescription(description); - - // Assert - verifyFrame(frame, description, "XXX", Id3v2Settings.defaultEncoding, ""); - } - - @test - public fromDescription_withLanguageWithoutEncoding() { - // Arrange - const description = "fux"; - const language = "bux"; - - // Act - const frame = CommentsFrame.fromDescription(description, language); - - // Assert - verifyFrame(frame, description, language, Id3v2Settings.defaultEncoding, ""); - } - - @test - public fromDescription_withLanguageWithEncoding() { - // Arrange - const description = "fux"; - const language = "bux"; - const encoding = StringType.Latin1; - - // Act - const frame = CommentsFrame.fromDescription(description, language, encoding); - - // Assert - verifyFrame(frame, description, language, encoding, ""); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -198,12 +146,57 @@ const verifyFrame = ( // Assert verifyFrame(frame, "fux", "eng", encoding, "bux"); } + + @test + public fromFields_noParams() { + // Act + const frame = CommentsFrame.fromFields(); + + // Assert + verifyFrame(frame, "", "XXX", Id3v2Settings.defaultEncoding, ""); + } + + @test + public fromFields_withDescription() { + // Act + const frame = CommentsFrame.fromFields("foo"); + + // Assert + verifyFrame(frame, "foo", "XXX", Id3v2Settings.defaultEncoding, ""); + } + + @test + public fromFields_withDescriptionText() { + // Act + const frame = CommentsFrame.fromFields("foo", "bar"); + + // Assert + verifyFrame(frame, "foo", "XXX", Id3v2Settings.defaultEncoding, "bar"); + } + + @test + public fromFields_withDescriptionTextLanguage() { + // Act + const frame = CommentsFrame.fromFields("foo", "bar", "baz"); + + // Assert + verifyFrame(frame, "foo", "baz", Id3v2Settings.defaultEncoding, "bar"); + } + + @test + public fromFields_withDescriptionTextLanguageEncoding() { + // Act + const frame = CommentsFrame.fromFields("foo", "bar", "baz", StringType.Hex); + + // Assert + verifyFrame(frame, "foo", "baz", StringType.Hex, "bar"); + } } @suite class Id3v2_CommentsFrame_PropertyTests { @test public description() { - const frame = getTestFrame(); + const frame = CommentsFrame.fromFields("bar", "foo", "eng", StringType.Latin1); const set = (v: string) => { frame.description = v; }; const get = () => frame.description; @@ -214,7 +207,7 @@ const verifyFrame = ( @test public language() { - const frame = getTestFrame(); + const frame = CommentsFrame.fromFields("bar", "foo", "eng", StringType.Latin1); const set = (v: string) => { frame.language = v; }; const get = () => frame.language; @@ -227,7 +220,7 @@ const verifyFrame = ( @test public text() { - const frame = getTestFrame(); + const frame = CommentsFrame.fromFields("bar", "foo", "eng", StringType.Latin1); const set = (v: string) => { frame.text = v; }; const get = () => frame.text; @@ -238,13 +231,11 @@ const verifyFrame = ( @test public textEncoding() { - const frame = getTestFrame(); + const frame = CommentsFrame.fromFields("bar", "foo", "eng", StringType.Latin1); - PropertyTests.propertyRoundTrip( - (v) => { frame.textEncoding = v; }, - () => frame.textEncoding, - StringType.UTF16 - ); + const set = (v: StringType) => { frame.textEncoding = v; }; + const get = () => frame.textEncoding; + PropertyTests.propertyRoundTrip(set, get, StringType.UTF16); } } @@ -271,8 +262,8 @@ const verifyFrame = ( @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -286,8 +277,8 @@ const verifyFrame = ( @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = CommentsFrame.fromDescription("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = CommentsFrame.fromFields("foo"); const frames = [frame1, frame2]; // Act @@ -301,9 +292,9 @@ const verifyFrame = ( @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = CommentsFrame.fromDescription("foo"); - const frame3 = CommentsFrame.fromDescription("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = CommentsFrame.fromFields("foo"); + const frame3 = CommentsFrame.fromFields("bar"); const frames = [frame1, frame2, frame3]; @@ -318,8 +309,8 @@ const verifyFrame = ( @test public filterFrames_allMatches() { // Arrange - const frame1 = CommentsFrame.fromDescription("foo"); - const frame2 = CommentsFrame.fromDescription("bar"); + const frame1 = CommentsFrame.fromFields("foo"); + const frame2 = CommentsFrame.fromFields("bar"); const frames = [frame1, frame2]; // Act @@ -333,8 +324,7 @@ const verifyFrame = ( @test public clone() { // Arrange - const frame = CommentsFrame.fromDescription("fux", "bux", StringType.UTF16BE); - frame.text = "qux"; + const frame = CommentsFrame.fromFields("foo", "bar", "baz", StringType.UTF16BE); // Act const output = frame.clone(); @@ -351,8 +341,7 @@ const verifyFrame = ( @params([4, StringType.UTF16BE], "v4_multibyte") public render([version, encoding]: [number, StringType]) { // Arrange - const frame = CommentsFrame.fromDescription("foo", "eng", encoding); - frame.text = "bar"; + const frame = CommentsFrame.fromFields("bar", "foo", "eng", encoding); // Act const output = frame.render(version); @@ -363,9 +352,9 @@ const verifyFrame = ( const expectedFieldBytes = ByteVector.concatenate( encoding, // Encoding ByteVector.fromString("eng", StringType.Latin1), // Language - ByteVector.fromString("foo", encoding), // Description + ByteVector.fromString("bar", encoding), // Description ByteVector.getTextDelimiter(encoding), // Delimiter - ByteVector.fromString("bar", encoding) // Comment text + ByteVector.fromString("foo", encoding) // Comment text ); const header = new Id3v2FrameHeader(FrameIdentifiers.COMM, Id3v2FrameFlags.None, expectedFieldBytes.length); const expectedBytes = ByteVector.concatenate(header.render(version), expectedFieldBytes); @@ -377,8 +366,7 @@ const verifyFrame = ( @params([4, StringType.UTF8], "v4") public render_utf8([version, outputEncoding]: [number, StringType]) { // Arrange - const frame = CommentsFrame.fromDescription("foo", "eng", StringType.UTF8); - frame.text = "bar"; + const frame = CommentsFrame.fromFields("bar", "foo", "eng", StringType.UTF8); // Act const output = frame.render(version); @@ -389,9 +377,9 @@ const verifyFrame = ( const expectedFieldBytes = ByteVector.concatenate( outputEncoding, // Encoding ByteVector.fromString("eng", StringType.Latin1), // Language - ByteVector.fromString("foo", outputEncoding), // Description + ByteVector.fromString("bar", outputEncoding), // Description ByteVector.getTextDelimiter(outputEncoding), // Delimiter - ByteVector.fromString("bar", outputEncoding) // Comment text + ByteVector.fromString("foo", outputEncoding) // Comment text ); const header = new Id3v2FrameHeader(FrameIdentifiers.COMM, Id3v2FrameFlags.None, expectedFieldBytes.length); const expectedBytes = ByteVector.concatenate(header.render(version), expectedFieldBytes); @@ -401,7 +389,7 @@ const verifyFrame = ( @test public render_descriptionOnly() { // Arrange - const frame = CommentsFrame.fromDescription("foo", "eng", StringType.Latin1); + const frame = CommentsFrame.fromFields("foo", "", "eng", StringType.Latin1); // Act const result = frame.render(4); @@ -423,8 +411,7 @@ const verifyFrame = ( @test public render_commentsOnly() { // Arrange - const frame = CommentsFrame.fromDescription("", "eng", StringType.Latin1); - frame.text = "foo"; + const frame = CommentsFrame.fromFields("", "foo", "eng", StringType.Latin1); // Act const result = frame.render(4); diff --git a/test-unit/id3v2/eventTimeCodeFrameTests.ts b/test-unit/id3v2/eventTimeCodeFrameTests.ts index 7e93515c..8813ff64 100644 --- a/test-unit/id3v2/eventTimeCodeFrameTests.ts +++ b/test-unit/id3v2/eventTimeCodeFrameTests.ts @@ -103,24 +103,6 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp return EventTimeCodeFrame.fromFieldBytes; } - @test - public fromEmpty() { - // Act - const output = EventTimeCodeFrame.fromEmpty(); - - // Assert - assertFrame(output, [], TimestampFormat.Unknown); - } - - @test - public fromTimestampFormat() { - // Act - const output = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds); - - // Assert - assertFrame(output, [], TimestampFormat.AbsoluteMilliseconds); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -186,13 +168,43 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp // Act / Assert assert.throws(() => EventTimeCodeFrame.fromFieldBytes(header, fieldBytes, version)); } + + @test + public fromFields_withNothing() { + // Act + const output = EventTimeCodeFrame.fromFields(); + + // Assert + assertFrame(output, [], TimestampFormat.Unknown); + } + + @test + public fromFields_withTimestampFormat() { + // Act + const output = EventTimeCodeFrame.fromFields(TimestampFormat.AbsoluteMilliseconds); + + // Assert + assertFrame(output, [], TimestampFormat.AbsoluteMilliseconds); + } + + @test + public fromFields_withTimestampFormatEvents() { + // Arrange + const events = [new EventTimeCode(EventType.Profanity, 12345)]; + + // Act + const output = EventTimeCodeFrame.fromFields(TimestampFormat.AbsoluteMilliseconds, events); + + // Assert + assertFrame(output, events, TimestampFormat.AbsoluteMilliseconds); + } } @suite class Id3v2_EventTimeCodeFrame_PropertyTests { @test public events() { // Arrange - const frame = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds); + const frame = EventTimeCodeFrame.fromFields(TimestampFormat.AbsoluteMilliseconds); const set = (v: EventTimeCode[]) => { frame.events = v; }; const get = () => frame.events; @@ -206,14 +218,12 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public timeStampFormat() { // Arrange - const frame = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds); + const frame = EventTimeCodeFrame.fromFields(TimestampFormat.AbsoluteMilliseconds); // Act / Assert - PropertyTests.propertyRoundTrip( - (v) => { frame.timestampFormat = v; }, - () => frame.timestampFormat, - TimestampFormat.AbsoluteMpegFrames - ); + const get = () => frame.timestampFormat; + const set = (v: TimestampFormat) => { frame.timestampFormat = v; }; + PropertyTests.propertyRoundTrip(set, get, TimestampFormat.AbsoluteMpegFrames); } } @@ -221,10 +231,9 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public clone() { // Arrange - const frame = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds); - const event1 = new EventTimeCode(EventType.Profanity, 123); - const event2 = new EventTimeCode(EventType.ProfanityEnd, 456); - frame.events = [event1, event2]; + const frame = EventTimeCodeFrame.fromFields( + TimestampFormat.AbsoluteMilliseconds, + [new EventTimeCode(EventType.Profanity, 123), new EventTimeCode(EventType.ProfanityEnd, 456)]); // Act const output = frame.clone(); @@ -255,8 +264,8 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -270,8 +279,8 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = EventTimeCodeFrame.fromEmpty(); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = EventTimeCodeFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -285,9 +294,9 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = EventTimeCodeFrame.fromEmpty(); - const frame3 = EventTimeCodeFrame.fromEmpty(); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = EventTimeCodeFrame.fromFields(); + const frame3 = EventTimeCodeFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -302,8 +311,8 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @test public filterFrames_allMatches() { // Arrange - const frame1 = EventTimeCodeFrame.fromEmpty(); - const frame2 = EventTimeCodeFrame.fromEmpty(); + const frame1 = EventTimeCodeFrame.fromFields(); + const frame2 = EventTimeCodeFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -319,7 +328,7 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp @params(4, "v4") public render_withoutEvents(version: number) { // Arrange - const frame = EventTimeCodeFrame.fromEmpty(); + const frame = EventTimeCodeFrame.fromFields(); frame.timestampFormat = TimestampFormat.AbsoluteMpegFrames; frame.events = []; @@ -347,7 +356,7 @@ const assertFrame = (frame: EventTimeCodeFrame, e: EventTimeCode[], t: Timestamp const event1 = new EventTimeCode(EventType.Profanity, 123); const event2 = new EventTimeCode(EventType.KeyChange, 456); - const frame = EventTimeCodeFrame.fromEmpty(); + const frame = EventTimeCodeFrame.fromFields(); frame.timestampFormat = TimestampFormat.AbsoluteMpegFrames; frame.events = [event2, event1]; // Force events to be sorted diff --git a/test-unit/id3v2/frameFactoryTests.ts b/test-unit/id3v2/frameFactoryTests.ts index e409e516..5fea1233 100644 --- a/test-unit/id3v2/frameFactoryTests.ts +++ b/test-unit/id3v2/frameFactoryTests.ts @@ -1,5 +1,6 @@ import {params, suite, test} from "@testdeck/mocha"; import {assert} from "chai"; +import {It, Mock, Times} from "typemoq"; import AttachmentFrame from "../../src/id3v2/frames/attachmentFrame"; import CommentsFrame from "../../src/id3v2/frames/commentsFrame"; @@ -26,9 +27,8 @@ import {FrameIdentifier, FrameIdentifiers} from "../../src/id3v2/frameIdentifier import {PictureType} from "../../src/picture"; import {RelativeVolumeFrame} from "../../src/id3v2/frames/relativeVolumeFrame"; import {SynchronizedLyricsFrame} from "../../src/id3v2/frames/synchronizedLyricsFrame"; -import {SynchronizedTextType, TimestampFormat} from "../../src/id3v2/utilTypes"; import {Testers} from "../utilities/testers"; -import {It, Mock, Times} from "typemoq"; +import {TimestampFormat} from "../../src/id3v2/utilTypes"; import {NumberUtils} from "../../src/utils"; @suite class FrameFactoryTests { @@ -138,7 +138,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_comm() { // Arrange - const data = CommentsFrame.fromDescription("foo").render(4); + const data = CommentsFrame.fromFields("foo", "bar").render(4); const file = TestFile.getFile(data); // Act @@ -151,7 +151,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_etco() { // Arrange - const data = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds).render(4); + const data = EventTimeCodeFrame.fromFields(TimestampFormat.AbsoluteMilliseconds).render(4); const file = TestFile.getFile(data); // Act @@ -194,7 +194,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_pcnt() { // Arrange - const data = PlayCountFrame.fromEmpty().render(4); + const data = PlayCountFrame.fromFields(BigInt(123)).render(4); const file = TestFile.getFile(data); // Act @@ -207,7 +207,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_popm() { // Arrange - const data = PopularimeterFrame.fromUser("foo").render(4); + const data = PopularimeterFrame.fromFields("foo", 123).render(4); const file = TestFile.getFile(data); // Act @@ -220,7 +220,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_priv() { // Arrange - const data = PrivateFrame.fromOwner("foo").render(4); + const data = PrivateFrame.fromFields("foo", ByteVector.fromUint(123)).render(4); const file = TestFile.getFile(data); // Act @@ -233,7 +233,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_rva2() { // Arrange - const data = RelativeVolumeFrame.fromIdentification("foo").render(4); + const data = RelativeVolumeFrame.fromFields("foo").render(4); const file = TestFile.getFile(data); // Act @@ -246,7 +246,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFrame_sylt() { // Arrange - const data = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Lyrics).render(4); + const data = SynchronizedLyricsFrame.fromFields("foo").render(4); const file = TestFile.getFile(data); // Act @@ -259,9 +259,8 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_textFrame() { // Arrange - const data = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - data.text = ["foo"]; - const file = TestFile.getFile(data.render(4)); + const data = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo"]).render(4); + const file = TestFile.getFile(data); // Act const output = Id3v2FrameFactory.createFrameFromFile(file, 0, 4, false); @@ -273,7 +272,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_txxx() { // Arrange - const data = UserTextInformationFrame.fromDescription("foo").render(4); + const data = UserTextInformationFrame.fromFields("foo", ["foo"]).render(4); const file = TestFile.getFile(data); // Act @@ -286,7 +285,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_ufid() { // Arrange - const data = UniqueFileIdentifierFrame.fromData("foo", ByteVector.fromByte(0x05)).render(4); + const data = UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromByte(0x05)).render(4); const file = TestFile.getFile(data); // Act @@ -301,7 +300,7 @@ import {NumberUtils} from "../../src/utils"; // Arrange const data = ByteVector.concatenate( 0x00, 0x00, - UniqueFileIdentifierFrame.fromData("foo", ByteVector.fromByte(0x05)).render(4) + UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromByte(0x05)).render(4) ); const file = TestFile.getFile(data); @@ -330,9 +329,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_urlFrame() { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = "foo"; - const data = frame.render(4); + const data = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, "foo").render(4); const file = TestFile.getFile(data); // Act @@ -342,6 +339,7 @@ import {NumberUtils} from "../../src/utils"; FrameFactoryTests.validateOutput(output, UrlLinkFrame, 4); } + @test public createFrameFromFile_user() { // Arrange const data = TermsOfUseFrame.fromFields("foo").render(4); @@ -357,7 +355,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_uslt() { // Arrange - const data = UnsynchronizedLyricsFrame.fromData("foo").render(4); + const data = UnsynchronizedLyricsFrame.fromFields("foo", "bar").render(4); const file = TestFile.getFile(data); // Act @@ -413,7 +411,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_customWithMatch() { // Arrange - const frame = PlayCountFrame.fromEmpty(); + const frame = PlayCountFrame.fromFields(); const data = frame.render(4); const file = TestFile.getFile(data); @@ -449,7 +447,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromFile_customWithoutMatch() { // Arrange - const frame = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromByteArray([0x01, 0x02, 0x03])); + const frame = UnknownFrame.fromFields(FrameIdentifiers.RVRB, ByteVector.fromByteArray([0x01, 0x02, 0x03])); const data = frame.render(4); const file = TestFile.getFile(data); @@ -653,7 +651,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_comm() { // Arrange - const data = CommentsFrame.fromDescription("foo").render(4); + const data = CommentsFrame.fromFields("foo", "bar").render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -665,7 +663,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_etco() { // Arrange - const data = EventTimeCodeFrame.fromTimestampFormat(TimestampFormat.AbsoluteMilliseconds).render(4); + const data = EventTimeCodeFrame.fromFields().render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -707,7 +705,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_pcnt() { // Arrange - const data = PlayCountFrame.fromEmpty().render(4); + const data = PlayCountFrame.fromFields(BigInt(123)).render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -719,7 +717,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_popm() { // Arrange - const data = PopularimeterFrame.fromUser("foo").render(4); + const data = PopularimeterFrame.fromFields("foo", 123).render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -731,7 +729,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_priv() { // Arrange - const data = PrivateFrame.fromOwner("foo").render(4); + const data = PrivateFrame.fromFields("foo", ByteVector.fromUint(123)).render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -743,7 +741,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_rva2() { // Arrange - const data = RelativeVolumeFrame.fromIdentification("foo").render(4); + const data = RelativeVolumeFrame.fromFields("foo").render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -755,7 +753,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_sylt() { // Arrange - const data = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Lyrics).render(4); + const data = SynchronizedLyricsFrame.fromFields("foo").render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -767,8 +765,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_textFrame() { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame.text = ["foo"]; + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo"]); const data = frame.render(4); // Act @@ -781,7 +778,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_txxx() { // Arrange - const data = UserTextInformationFrame.fromDescription("foo").render(4); + const data = UserTextInformationFrame.fromFields("foo", ["bar"]).render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -793,7 +790,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_ufid() { // Arrange - const data = UniqueFileIdentifierFrame.fromData("foo", ByteVector.fromByte(0x08)).render(4); + const data = UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromByte(0x08)).render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -838,8 +835,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_urlFrame() { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = "foo"; + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, "foo"); const data = frame.render(4); // Act @@ -864,7 +860,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_uslt() { // Arrange - const data = UnsynchronizedLyricsFrame.fromData("foo").render(4); + const data = UnsynchronizedLyricsFrame.fromFields("foo", "bar").render(4); // Act const output = Id3v2FrameFactory.createFrameFromTagBytes(data, 0, 4, false); @@ -917,7 +913,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_customWithMatch() { // Arrange - const frame = PlayCountFrame.fromEmpty(); + const frame = PlayCountFrame.fromFields(BigInt(123)); const data = frame.render(4); const fieldBytes = data.subarray(Id3v2FrameHeader.getBaseSize(4)); @@ -952,7 +948,7 @@ import {NumberUtils} from "../../src/utils"; @test public createFrameFromTagBytes_customWithoutMatch() { // Arrange - const frame = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromByteArray([0x01, 0x02, 0x03])); + const frame = UnknownFrame.fromFields(FrameIdentifiers.RVRB, ByteVector.fromByteArray([0x01, 0x02, 0x03])); const data = frame.render(4); const fieldBytes = data.subarray(Id3v2FrameHeader.getBaseSize(4)); diff --git a/test-unit/id3v2/genreFrameTests.ts b/test-unit/id3v2/genreFrameTests.ts index 707cbbfe..8423545a 100644 --- a/test-unit/id3v2/genreFrameTests.ts +++ b/test-unit/id3v2/genreFrameTests.ts @@ -1,4 +1,4 @@ -import {suite, test} from "@testdeck/mocha"; +import {params, suite, test} from "@testdeck/mocha"; import {assert} from "chai"; import Frame from "../../src/id3v2/frames/frame"; @@ -11,6 +11,15 @@ import {Id3v2FrameFlags, Id3v2FrameHeader} from "../../src/id3v2/frames/frameHea import {FrameIdentifiers} from "../../src/id3v2/frameIdentifiers"; import {Testers} from "../utilities/testers"; +const assertFrame = (frame: GenreFrame, text: string[], textEncoding: StringType) => { + assert.isOk(frame); + assert.instanceOf(frame, GenreFrame); + assert.strictEqual(frame.frameId, FrameIdentifiers.TCON); + + assert.deepStrictEqual(frame.text, text); + assert.strictEqual(frame.textEncoding, textEncoding); +} + @suite class Id3v2_GenreFrameTests extends FrameConstructorTests { @@ -19,37 +28,36 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { // region Property tests @test - public fromEncoding() { + public fromFields_nothing() { // Act - const frame = GenreFrame.fromEncoding(StringType.UTF16BE); + const frame = GenreFrame.fromFields(); // Assert - assert.isOk(frame); - assert.instanceOf(frame, GenreFrame); - assert.strictEqual(frame.frameId, FrameIdentifiers.TCON); - - assert.deepStrictEqual(frame.text, []); - assert.strictEqual(frame.textEncoding, StringType.UTF16BE); + assertFrame(frame, [], Id3v2Settings.defaultEncoding); } @test - public text_returnsCopy() { - // Arrange - const frame = GenreFrame.fromEncoding(); - frame.text = ["foo", "bar"]; + public fromFields_withText() { + // Act + const frame = GenreFrame.fromFields(["foo", "bar"]); + // Assert + assertFrame(frame, ["foo", "bar"], Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withTextEncoding() { // Act - const text = frame.text; - text.push("baz"); + const frame = GenreFrame.fromFields(["foo", "bar"], StringType.UTF16BE); // Assert - assert.deepStrictEqual(frame.text, ["foo", "bar"]); + assertFrame(frame, ["foo", "bar"], StringType.UTF16BE); } @test public text_setFalsyReturnsEmptyArray() { // Arrange - const frame = GenreFrame.fromEncoding(); + const frame = GenreFrame.fromFields(["foo", "bar"]); // Act frame.text = undefined; @@ -61,7 +69,7 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public textEncoding() { // Arrange - const frame = GenreFrame.fromEncoding(); + const frame = GenreFrame.fromFields(["foo", "bar"]); // Act frame.textEncoding = StringType.UTF16BE; @@ -74,238 +82,159 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { // region Parse Tests - @test - public parse_singleTerm_string() { - this.testFrameParseV2V3("Classical", ["Classical"]); - } - - @test - public parse_singleTerm_invalidParentheses() { - this.testFrameParseV2V3("(foo)", ["(foo)"]); - } - - @test - public parse_singleTerm_singleStandardNumber() { - this.testFrameParseV2V3("(32)", ["Classical"]); - } - - @test - public parse_singleTerm_singleRemixCover() { - this.testFrameParseV2V3("(CR)", ["Cover"]); - this.testFrameParseV2V3("(RX)", ["Remix"]); - } - - @test - public parse_singleTerm_singleStandardNumber_stringRefinement() { - this.testFrameParseV2V3("(32)foo", ["Classical foo"]); - } - - @test - public parse_singleTerm_singleStandardNumber_stringRefinementWithEscape() { - this.testFrameParseV2V3("(32)f((oo", ["Classical f(oo"]); - } - - @test - public parse_singleTerm_singleStandardNumber_stringRefinementWithoutEscape() { - this.testFrameParseV2V3("(32)f(oo", ["Classical f(oo"]); - } - - @test - public parse_singleTerm_multipleStandardNumber() { - this.testFrameParseV2V3("(32)(33)", ["Classical", "Instrumental"]); - } - - @test - public parse_singleTerm_multipleRemixCover() { - this.testFrameParseV2V3("(CR)(RX)", ["Cover", "Remix"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementOnFirst() { - this.testFrameParseV2V3("(32)foo(33)", ["Classical foo", "Instrumental"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementOnSecond() { - this.testFrameParseV2V3("(32)(33)bar", ["Classical", "Instrumental bar"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementOnBoth() { - this.testFrameParseV2V3("(32)foo(33)bar", ["Classical foo", "Instrumental bar"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithEscapeOnFirst() { - this.testFrameParseV2V3("(32)f((oo(33)", ["Classical f(oo", "Instrumental"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithEscapeOnSecond() { - this.testFrameParseV2V3("(32)(33)b((ar", ["Classical", "Instrumental b(ar"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithEscapeOnBoth() { - this.testFrameParseV2V3("(32)f((oo(33)b((ar", ["Classical f(oo", "Instrumental b(ar"]); - } - - @test - public parse_singleTerm_multipleRemixCover_stringRefinementWithEscapeOnBoth() { - this.testFrameParseV2V3("(CR)f((oo(RX)b((ar", ["Cover f(oo", "Remix b(ar"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithoutEscapeOnFirst() { - this.testFrameParseV2V3("(32)f(oo(33)", ["Classical f(oo(33)"]); + @params(2, "v2") + @params(3, "v3") + public parse_v23_emptyFrame(version: number) { + this.testFrameParse(version, ByteVector.concatenate(StringType.UTF16BE), []); } - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithoutEscapeOnSecond() { - this.testFrameParseV2V3("(32)(33)b(ar", ["Classical", "Instrumental b(ar"]); - } - - @test - public parse_singleTerm_multipleStandardNumber_stringRefinementWithoutEscapeOnBoth() { - this.testFrameParseV2V3("(32)f(oo(33)b(ar", ["Classical f(oo(33)b(ar"]); - } - - @test - public parse_singleTerm_multipleRemixCover_stringRefinementWithoutEscapeOnBoth() { - this.testFrameParseV2V3("(CR)f(oo(RX)b(ar", ["Cover f(oo(RX)b(ar"]); - } - - @test - public parse_singleTerm_singleNonstandardNumber_nonstandardNumericDisabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: false}; - this.testFrameParseV2V3WithSettings(partialSettings, "32", ["32"]); - } - - @test - public parse_stringTerm_singleNonstandardNumber_nonstandardNumericEnabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: true}; - this.testFrameParseV2V3WithSettings(partialSettings, "32", ["Classical"]); - } - - @test - public parse_singleTerm_singleNonstandardNumber_withString_nonstandardNumericDisabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: false}; - this.testFrameParseV2V3WithSettings(partialSettings, "32foo", ["32foo"]); - } - - @test - public parse_singleTerm_singleNonstandardNumber_withString_nonstandardNumericEnabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: true}; - this.testFrameParseV2V3WithSettings(partialSettings, "32foo", ["32foo"]); - } - - @test - public parse_singleTerm_multipleNonstandardNumber_withString_nonStandardNumericDisabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: false}; - this.testFrameParseV2V3WithSettings(partialSettings, "32foo33bar", ["32foo33bar"]); - } - - @test - public parse_singleTerm_multipleNonstandardNumber_withString_nonStandardNumericEnabled() { - const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: true}; - this.testFrameParseV2V3WithSettings(partialSettings, "32foo33bar", ["32foo33bar"]); - } + @params(2, "v2") + @params(3, "v3") + public parse_v23_StartsWithDelimiter(version: number) { + const payload = ByteVector.concatenate( + StringType.UTF16BE, + ByteVector.getTextDelimiter(StringType.UTF16BE), + ByteVector.fromString("foo", StringType.UTF16BE) + ); - @test - public parse_multipleTerms_nonStandardSeparatorEnabled() { + this.testFrameParse(version, payload, []); + } + + @params([2, "Classical", ["Classical"]], "string_v2") + @params([3, "Classical", ["Classical"]], "string_v3") + @params([2, "(foo)", ["(foo)"]], "invalidParentheses_v2") + @params([3, "(foo)", ["(foo)"]], "invalidParentheses_v3") + public parse_v23_singleTerm_general([version, input, output]: [number, string, string[]]) { + this.testFrameParse(version, input, output); + } + + @params([2, "(32)", ["Classical"]], "v2") + @params([3, "(32)", ["Classical"]], "v3") + @params([2, "(32)foo", ["Classical foo"]], "Refinement_v2") + @params([3, "(32)foo", ["Classical foo"]], "Refinement_v3") + @params([2, "(32)f((oo", ["Classical f(oo"]], "Refinement_Escaped_v2") + @params([3, "(32)f((oo", ["Classical f(oo"]], "Refinement_Escaped_v3") + @params([2, "(32)f(oo", ["Classical f(oo"]], "Refinement_Unescaped_v2") + @params([3, "(32)f(oo", ["Classical f(oo"]], "Refinement_Unescaped_v3") + public parse_v23_singleTerm_oneStandardNumber([version, input, output]: [number, string, string[]]) { + this.testFrameParse(version, input, output); + } + + @params([2, "(32)(33)", ["Classical", "Instrumental"]], "v2") + @params([3, "(32)(33)", ["Classical", "Instrumental"]], "v2") + @params([2, "(32)foo(33)", ["Classical foo", "Instrumental"]], "1stRefinement_v2") + @params([3, "(32)foo(33)", ["Classical foo", "Instrumental"]], "1stRefinement_v3") + @params([2, "(32)f((oo(33)", ["Classical f(oo", "Instrumental"]], "1stRefinement_Escaped_v2") + @params([3, "(32)f((oo(33)", ["Classical f(oo", "Instrumental"]], "1stRefinement_Escaped_v3") + @params([2, "(32)f(oo(33)", ["Classical f(oo(33)"]], "1stRefinement_Unescaped_v2") + @params([3, "(32)f(oo(33)", ["Classical f(oo(33)"]], "1stRefinement_Unescaped_v3") + @params([2, "(32)(33)bar", ["Classical", "Instrumental bar"]], "2ndRefinement_v2") + @params([3, "(32)(33)bar", ["Classical", "Instrumental bar"]], "2ndRefinement_v3") + @params([2, "(32)(33)b((ar", ["Classical", "Instrumental b(ar"]], "2ndRefinement_Escaped_v2") + @params([3, "(32)(33)b((ar", ["Classical", "Instrumental b(ar"]], "2ndRefinement_Escaped_v3") + @params([2, "(32)(33)b(ar", ["Classical", "Instrumental b(ar"]], "2ndRefinement_Unescaped_v2") + @params([3, "(32)(33)b(ar", ["Classical", "Instrumental b(ar"]], "2ndRefinement_Unescaped_v3") + @params([2, "(32)foo(33)bar", ["Classical foo", "Instrumental bar"]], "BothRefinement_v2") + @params([3, "(32)foo(33)bar", ["Classical foo", "Instrumental bar"]], "BothRefinement_v3") + @params([2, "(32)f((oo(33)b((ar", ["Classical f(oo", "Instrumental b(ar"]], "BothRefinement_Escaped_v2") + @params([3, "(32)f((oo(33)b((ar", ["Classical f(oo", "Instrumental b(ar"]], "BothRefinement_Escaped_v3") + @params([2, "(32)f(oo(33)b(ar", ["Classical f(oo(33)b(ar"]], "BothRefinement_Unescaped_v2") + @params([3, "(32)f(oo(33)b(ar", ["Classical f(oo(33)b(ar"]], "BothRefinement_Unescaped_v3") + public parse_v23_singleTerm_multipleStandardNumber([version, input, output]: [number, string, string[]]) { + this.testFrameParse(version, input, output); + } + + @params([2, "(CR)", ["Cover"]], "Single_v2") + @params([3, "(CR)", ["Cover"]], "Single_v3") + @params([2, "(RX)", ["Remix"]], "Single_v2") + @params([3, "(RX)", ["Remix"]], "Single_v3") + @params([2, "(CR)(RX)", ["Cover", "Remix"]], "Multiple_v2") + @params([3, "(CR)(RX)", ["Cover", "Remix"]], "Multiple_v3") + @params([2, "(CR)f((oo(RX)b((ar", ["Cover f(oo", "Remix b(ar"]], "Multiple_EscapedRefinement_v2") + @params([3, "(CR)f((oo(RX)b((ar", ["Cover f(oo", "Remix b(ar"]], "Multiple_EscapedRefinement_v3") + @params([2, "(CR)f(oo(RX)b(ar", ["Cover f(oo(RX)b(ar"]], "Multiple_UnescapedRefinement_v2") + @params([3, "(CR)f(oo(RX)b(ar", ["Cover f(oo(RX)b(ar"]], "Multiple_UnescapedRefinement_v3") + public parse_v23_singleTerm_remixCover([version, input, output]: [number, string, string[]]) { + this.testFrameParse(version, input, output); + } + + @params([2, false, "32", ["32"]], "disabled_v2") + @params([3, false, "32", ["32"]], "disabled_v3") + @params([2, true, "32", ["Classical"]], "enabled_v2") + @params([3, true, "32", ["Classical"]], "enabled_v3") + @params([2, false, "32foo", ["32foo"]], "singleWithString_disabled_v2") + @params([3, false, "32foo", ["32foo"]], "singleWithString_disabled_v3") + @params([2, true, "32foo", ["32foo"]], "singleWithString_enabled_v2") + @params([3, true, "32foo", ["32foo"]], "singleWithString_enabled_v3") + @params([2, false, "32foo33bar", ["32foo33bar"]], "multipleWithString_disabled_v2") + @params([3, false, "32foo33bar", ["32foo33bar"]], "multipleWithString_disabled_v3") + @params([2, true, "32foo33bar", ["32foo33bar"]], "multipleWithString_enabled_v2") + @params([3, true, "32foo33bar", ["32foo33bar"]], "multipleWithString_enabled_v3") + public parse_v23_singleTerm_nonstandardNumber( + [version, numericGenres, input, output]: [number, boolean, string, string[]] + ) { + const partialSettings: PartialSettings = {useNonStandardV2V3NumericGenres: numericGenres}; + this.testFrameParse_v23WithSettings(version, partialSettings, input, output); + } + + @params(2, "v2") + @params(3, "v3") + public parse_v23_multipleTerms_nonStandardSeparatorEnabled(version: number) { const partialSettings: PartialSettings = {useNonStandardV2V3GenreSeparators: true}; - this.testFrameParseV2V3WithSettings( + this.testFrameParse_v23WithSettings( + version, partialSettings, "(32)Fux(33)Bux;34/Foo;Bar/Baz", ["Classical Fux", "Instrumental Bux", "Acid", "Foo", "Bar", "Baz"] ); } - @test - public parse_multipleTerms_nonStandardSeparatorDisabled() { + @params(2, "v2") + @params(3, "v3") + public parse_v23_multipleTerms_nonStandardSeparatorDisabled(version: number) { const partialSettings: PartialSettings = {useNonStandardV2V3GenreSeparators: false}; - this.testFrameParseV2V3WithSettings( + this.testFrameParse_v23WithSettings( + version, partialSettings, "(32)Fux(33)Bux;34/Foo;Bar/Baz", ["Classical Fux", "Instrumental Bux;34/Foo;Bar/Baz"] ); } - @test - public parse_v2V3EmptyFrame() { - this.testFrameParseV2V3(ByteVector.concatenate(StringType.UTF16BE), []); - } - - @test - public parse_v2V3StartsWithDelimiter() { - const payload = ByteVector.concatenate( - StringType.UTF16BE, - ByteVector.getTextDelimiter(StringType.UTF16BE), - ByteVector.fromString("foo", StringType.UTF16BE) - ); - - this.testFrameParseV2V3(payload, []); - } - - @test - public parse_v4ListOfStrings() { + @params(2, "v2") + @params(3, "v3") + public parse_v23_multipleTerm_trailingNulls(version: number) { const payload = ByteVector.concatenate( StringType.UTF16BE, - ByteVector.fromString("32", StringType.UTF16BE), - ByteVector.getTextDelimiter(StringType.UTF16BE), ByteVector.fromString("(32)", StringType.UTF16BE), ByteVector.getTextDelimiter(StringType.UTF16BE), - ByteVector.fromString("some genre", StringType.UTF16BE) + ByteVector.getTextDelimiter(StringType.UTF16BE) ); - this.testFrameParse(4, payload, [ - "Classical", - "(32)", - "some genre" - ]); - } - - @test - public parse_v4CoverRemix() { - this.testFrameParseV4(["CR"], ["Cover"]); - this.testFrameParseV4(["RX"], ["Remix"]); - } - - @test - public parse_v4CoverRemixWithRefinement() { - this.testFrameParseV4(["CR foo", "RX bar"], ["CR foo", "RX bar"]); - } - - @test - public parse_v4CoverRemixWithParentheses() { - this.testFrameParseV4(["(CR)", "(RX)"], ["(CR)", "(RX)"]); - } - - @test - public parse_v4CoverRemixWithEscapedParentheses() { - this.testFrameParseV4(["CR f((oo", "RX b((ar"], ["CR f((oo", "RX b((ar"]); + this.testFrameParse(version, payload, ["Classical"]); } @test - public parse_v4CoverRemixWithUnescapedParentheses() { - this.testFrameParseV4(["CR f(oo", "RX b(ar"], ["CR f(oo", "RX b(ar"]); + public parse_v4_listOfStrings() { + this.testFrameParse_v4(["32", "(32)", "some genre"], ["Classical", "(32)", "some genre"]); } - @test - public parse_v2V3WithTrailingNulls() { - const payload = ByteVector.concatenate( - StringType.UTF16BE, - ByteVector.fromString("(32)", StringType.UTF16BE), - ByteVector.getTextDelimiter(StringType.UTF16BE), - ByteVector.getTextDelimiter(StringType.UTF16BE) - ); - - this.testFrameParse(2, payload, ["Classical"]); - this.testFrameParse(3, payload, ["Classical"]); + @params([["CR"], ["Cover"]], "cover") + @params([["RX"], ["Remix"]], "remix") + @params([["CR foo"], ["CR foo"]], "cover_refined") + @params([["RX foo"], ["RX foo"]], "remix_refined") + @params([["(CR)"], ["(CR)"]], "cover_parenthesis") + @params([["(RX)"], ["(RX)"]], "remox_parenthesis") + @params([["CR f((oo"], ["CR f((oo"]], "cover_escaped") + @params([["RX f((oo"], ["RX f((oo"]], "remix_escaped") + @params([["CR f(oo"], ["CR f(oo"]], "cover_unescaped") + @params([["RX f(oo"], ["RX f(oo"]], "remix_unescaped") + public parse_v4_coverRemix([input, output]: [string[], string[]]) { + this.testFrameParse_v4(input, output); } @test - public parse_v4WithTrailingNulls() { + public parse_v4_trailingNulls() { const payload = ByteVector.concatenate( StringType.UTF16BE, ByteVector.fromString("32", StringType.UTF16BE), @@ -322,131 +251,75 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { // region Render tests - @test - public render_singleTerm_string() { - this.testFrameRenderV2V3(["foobarbaz"], "foobarbaz"); - } - - @test - public render_singleTerm_paren() { - this.testFrameRenderV2V3(["f()()"], "f(()(()"); - } - - @test - public render_singleTerm_numericString_useNumericGenresEnabled() { + @params([2, ["foobarbaz"], "foobarbaz"], "v2") + @params([3, ["foobarbaz"], "foobarbaz"], "v3") + @params([2, ["f()()"], "f(()(()"], "singleParentheses_v2") + @params([3, ["f()()"], "f(()(()"], "singleParentheses_v3") + @params([2, ["foo","bar","baz"], "foo;bar;baz"], "multiple_v2") + @params([3, ["foo","bar","baz"], "foo;bar;baz"], "multiple_v3") + public render_v23_general([version, input, output]: [number, string[], string]) { + this.testFrameRender(version, input, output); + } + + @params([2, ["Classical"], "(32)"], "single_v2") + @params([3, ["Classical"], "(32)"], "single_v3") + @params([2, ["Cover"], "(CR)"], "singleCover_v2") + @params([3, ["Cover"], "(CR)"], "singleCover_v3") + @params([2, ["Remix"], "(RX)"], "singleRemix_v2") + @params([3, ["Remix"], "(RX)"], "singleRemix_v3") + @params([2, ["Classical", "Instrumental"], "(32)(33)"], "multiple_v2") + @params([3, ["Classical", "Instrumental"], "(32)(33)"], "multiple_v3") + @params([2, ["Classical foo", "Instrumental bar"], "Classical foo;Instrumental bar"], "multipleWithRefinement_v2") + @params([3, ["Classical foo", "Instrumental bar"], "Classical foo;Instrumental bar"], "multipleWithRefinement_v3") + public render_v23_numericString_useNumericGenresEnabled( + [version, input, output]: [number, string[], string] + ) { const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV2V3WithSettings(settings, ["Classical"], "(32)"); - } - - @test - public render_singleTerm_coverRemixString_useNumericGenresEnabled() { - const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV2V3WithSettings(settings, ["Cover"], "(CR)"); - this.testFrameRenderV2V3WithSettings(settings, ["Remix"], "(RX)"); - } - - @test - public render_singleTerm_numericString_useNumericGenresDisabled() { + this.testFrameRender_v23WithSettings(version, settings, input, output); + } + + @params([2, ["Classical"], "Classical"], "single_v2") + @params([3, ["Classical"], "Classical"], "single_v3") + @params([2, ["Cover"], "Cover"], "singleCover_v2") + @params([3, ["Cover"], "Cover"], "singleCover_v3") + @params([2, ["Remix"], "Remix"], "singleRemix_v2") + @params([3, ["Remix"], "Remix"], "singleRemix_v3") + @params([2, ["Classical", "Instrumental"], "Classical;Instrumental"], "multiple_v2") + @params([3, ["Classical", "Instrumental"], "Classical;Instrumental"], "multiple_v3") + @params([2, ["Classical foo", "Instrumental bar"], "Classical foo;Instrumental bar"], "multipleWithRefinement_v2") + @params([3, ["Classical foo", "Instrumental bar"], "Classical foo;Instrumental bar"], "multipleWithRefinement_v3") + public render_v23_singleTerm_numericString_useNumericGenresDisabled( + [version, input, output]: [number, string[], string] + ) { const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV2V3WithSettings(settings, ["Classical"], "Classical"); + this.testFrameRender_v23WithSettings(version, settings, input, output); } - @test - public render_singleTerm_coverRemixString_useNumericGenresDisabled() { - const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV2V3WithSettings(settings, ["Cover"], "Cover"); - this.testFrameRenderV2V3WithSettings(settings, ["Remix"], "Remix"); - } - - @test - public render_multipleTerms_string() { - this.testFrameRenderV2V3(["foo","bar","baz"], "foo;bar;baz"); + @params([["foobarbaz"], ["foobarbaz"]], "single") + @params([["foo", "bar", "baz"], ["foo", "bar", "baz"]], "multiple") + @params([["foo", undefined, "", "bar"], ["foo", undefined, "", "bar"]], "multipleWithFalsy") + public render_v4_general([input, output]: [string[], string[]]) { + this.testFrameRender_v4(input, output); } - @test - public render_multipleTerms_numericGenre_useNumericGenresEnabled() { + @params([["Classical"], ["32"]], "single") + @params([["Cover"], ["CR"]], "cover") + @params([["Remix"], ["RX"]], "remix") + @params([["Classical", "Instrumental", "foo"], ["32", "33", "foo"]], "multiple") + @params([["Classical foo", "Instrumental foo"], ["Classical foo", "Instrumental foo"], "multipleWithRefinement"]) + public render_v4_numericString_useNumericGenresEnabled([input, output]: [string[], string[]]) { const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV2V3WithSettings(settings, ["Classical", "Instrumental"], "(32)(33)"); + this.testFrameRender_v4WithSettings(settings, input, output); } - @test - public render_multipleTerms_numericGenre_useNumericGenresDisabled() { + @params([["Classical"], ["Classical"]], "single") + @params([["Cover"], ["CR"]], "cover") // @TODO: Quadruple check this is correct if numeric genres are disabled + @params([["Remix"], ["RX"]], "remix") + @params([["Classical", "Instrumental", "foo"], ["Classical", "Instrumental", "foo"]], "multiple") + @params([["Classical foo", "Instrumental foo"], ["Classical foo", "Instrumental foo"], "multipleWithRefinement"]) + public render_v4_numericString_useNumericGenresDisabled([input, output]: [string[], string[]]) { const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV2V3WithSettings(settings, ["Classical", "Instrumental"], "Classical;Instrumental"); - } - - @test - public render_multipleTerms_numericGenreWithRefinement() { - this.testFrameRenderV2V3(["Classical foo", "Instrumental bar"], "Classical foo;Instrumental bar"); - } - - @test - public render_v4SingleTermString() { - this.testFrameRenderV4(["foobarbaz"], ["foobarbaz"]); - } - - @test - public render_v4MultipleTermsString() { - this.testFrameRenderV4(["foo","bar","baz"], ["foo","bar","baz"]); - } - - @test - public render_v4SingleTerm_numericString_useNumericGenresEnabled() { - const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV4WithSettings(settings, ["Classical"], ["32"]); - } - - @test - public render_v4SingleTerm_numericString_useNumericGenresDisabled() { - const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV4WithSettings(settings, ["Classical"], ["Classical"]); - } - - @test - public render_v4SingleTerm_coverRemixString_useNumericGenresEnabled() { - const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV4WithSettings(settings, ["Cover"], ["CR"]); - this.testFrameRenderV4WithSettings(settings, ["Remix"], ["RX"]); - } - - @test - public render_v4SingleTerm_coverRemixString_useNumericGenresDisabled() { - const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV4WithSettings(settings, ["Cover"], ["CR"]); - this.testFrameRenderV4WithSettings(settings, ["Remix"], ["RX"]); - } - - @test - public render_v4MultipleTerms_numericGenre_useNumericGenresEnabled() { - const settings: PartialSettings = { useNumericGenres: true }; - this.testFrameRenderV4WithSettings( - settings, - ["Classical", "Instrumental", "foo"], - ["32", "33", "foo"] - ); - } - - @test - public render_v4MultipleTerms_numericGenre_useNumericGenresDisabled() { - const settings: PartialSettings = { useNumericGenres: false }; - this.testFrameRenderV4WithSettings( - settings, - ["Classical", "Instrumental", "foo"], - ["Classical", "Instrumental", "foo"] - ); - } - - @test - public render_v4MultipleTerms_numericGenreWithRefinement() { - this.testFrameRenderV4( - ["Classical foo", "Instrumental bar"], - ["Classical foo", "Instrumental bar"] - ); - } - - @test - public render_v4EmptyTerms() { - this.testFrameRenderV4(["foo", undefined, "", "bar"], ["foo", undefined, "", "bar"]); + this.testFrameRender_v4WithSettings(settings, input, output); } // endregion @@ -456,20 +329,13 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public clone_returnsCopy() { // Arrange - const frame = GenreFrame.fromEncoding(StringType.UTF16BE); - frame.text = ["foo", "bar"]; + const frame = GenreFrame.fromFields(["foo", "bar"], StringType.UTF16BE); // Act const output = frame.clone(); // Assert - assert.isOk(output); - assert.instanceOf(output, GenreFrame); - assert.notStrictEqual(output, frame); - - assert.strictEqual(output.frameId, FrameIdentifiers.TCON); - assert.deepStrictEqual(output.text, ["foo", "bar"]); - assert.strictEqual(output.textEncoding, StringType.UTF16BE); + assertFrame(output, frame.text, frame.textEncoding); } @test @@ -494,8 +360,8 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -509,8 +375,8 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = GenreFrame.fromEncoding(StringType.UTF16); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = GenreFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -524,9 +390,9 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = GenreFrame.fromEncoding(StringType.UTF16); - const frame3 = GenreFrame.fromEncoding(StringType.UTF16BE); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = GenreFrame.fromFields(); + const frame3 = GenreFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -541,8 +407,8 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public filterFrames_allMatches() { // Arrange - const frame1 = GenreFrame.fromEncoding(StringType.Hex); - const frame2 = GenreFrame.fromEncoding(StringType.Latin1); + const frame1 = GenreFrame.fromFields(); + const frame2 = GenreFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -556,8 +422,7 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { @test public toString_returnsSemicolonSeparatedText() { // Arrange - const frame = GenreFrame.fromEncoding(); - frame.text = ["foo", "bar"]; + const frame = GenreFrame.fromFields(["foo", "bar"]); // Act const output = frame.toString(); @@ -568,6 +433,21 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { // endregion + private getDelimitedStrings(fields: string[]): Array { + const parts = []; + for (let i = 0; i < fields.length; i++) { + if (i !== 0) { + parts.push(ByteVector.getTextDelimiter(StringType.UTF16BE)); + } + + if (fields[i]) { + parts.push(ByteVector.fromString(fields[i], StringType.UTF16BE)); + } + } + + return parts; + } + private testFrameParse(tagVersion: number, payload: string|ByteVector, expected: string[]) { // Arrange const fieldBytes = payload instanceof ByteVector @@ -577,23 +457,22 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { // Act const frame = GenreFrame.fromFieldBytes(header, fieldBytes, tagVersion); - const values = frame.text; // Assert - assert.deepStrictEqual(values, expected); + assertFrame(frame, expected, StringType.UTF16BE); } - private testFrameParseV2V3(payload: string|ByteVector, expected: string[]) { - this.testFrameParse(2, payload, expected); - this.testFrameParse(3, payload, expected); - } - - private testFrameParseV2V3WithSettings(settings: PartialSettings, payload: string, expected: string[]) { - const action = () => { this.testFrameParseV2V3(payload, expected); }; + private testFrameParse_v23WithSettings( + version: number, + settings: PartialSettings, + payload: string, + expected: string[] + ) { + const action = () => { this.testFrameParse(version, payload, expected); }; this.testWithSettings(settings, action); } - private testFrameParseV4(fields: string[], expected: string[]) { + private testFrameParse_v4(fields: string[], expected: string[]) { const payload = ByteVector.concatenate( StringType.UTF16BE, ... this.getDelimitedStrings(fields) @@ -604,9 +483,7 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { private testFrameRender(tagVersion: number, fields: string[], expected: string) { // Arrange - const frame = GenreFrame.fromEncoding(); - frame.textEncoding = StringType.UTF16BE; - frame.text = fields; + const frame = GenreFrame.fromFields(fields, StringType.UTF16BE); // Act const result = frame.render(tagVersion); @@ -616,23 +493,24 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { StringType.UTF16BE, ByteVector.fromString(expected, StringType.UTF16BE) ); - - const expectedHeader = Id3v2FrameHeader.fromFrameIdentifier(FrameIdentifiers.TCON); - expectedHeader.frameSize = expectedBody.length; - - const expectedBytes = ByteVector.concatenate( - expectedHeader.render(tagVersion), - expectedBody - ); + const expectedHeader = new Id3v2FrameHeader(FrameIdentifiers.TCON, Id3v2FrameFlags.None, expectedBody.length); + const expectedBytes = ByteVector.concatenate(expectedHeader.render(tagVersion), expectedBody); Testers.bvEqual(result, expectedBytes); } - private testFrameRenderV4(fields: string[], expected: string[]) { + private testFrameRender_v23WithSettings( + version: number, + settings: PartialSettings, + fields: string[], expected: string + ) { + const action = () => { this.testFrameRender(version, fields, expected); }; + this.testWithSettings(settings, action); + } + + private testFrameRender_v4(fields: string[], expected: string[]) { // Arrange - const frame = GenreFrame.fromEncoding(); - frame.textEncoding = StringType.UTF16BE; - frame.text = fields; + const frame = GenreFrame.fromFields(fields, StringType.UTF16BE); // Act const result = frame.render(4); @@ -642,48 +520,21 @@ class Id3v2_GenreFrameTests extends FrameConstructorTests { StringType.UTF16BE, ... this.getDelimitedStrings(expected) ); - - const expectedHeader = Id3v2FrameHeader.fromFrameIdentifier(FrameIdentifiers.TCON); - expectedHeader.frameSize = expectedBody.length; - - const expectedBytes = ByteVector.concatenate( - expectedHeader.render(4), - expectedBody - ); + const expectedHeader = new Id3v2FrameHeader(FrameIdentifiers.TCON, Id3v2FrameFlags.None, expectedBody.length); + const expectedBytes = ByteVector.concatenate(expectedHeader.render(4), expectedBody); Testers.bvEqual(result, expectedBytes); } - private testFrameRenderV2V3(fields: string[], expected: string) { - this.testFrameRender(2, fields, expected); - this.testFrameRender(3, fields, expected); - } - - private testFrameRenderV2V3WithSettings(settings: PartialSettings, fields: string[], expected: string) { - const action = () => { this.testFrameRenderV2V3(fields, expected); }; - this.testWithSettings(settings, action); - } - - private testFrameRenderV4WithSettings(settings: PartialSettings, fields: string[], expected: string[]) { - const action = () => { this.testFrameRenderV4(fields, expected); }; + private testFrameRender_v4WithSettings( + settings: PartialSettings, + fields: string[], + expected: string[] + ) { + const action = () => { this.testFrameRender_v4(fields, expected); }; this.testWithSettings(settings, action); } - private getDelimitedStrings(fields: string[]): Array { - const parts = []; - for (let i = 0; i < fields.length; i++) { - if (i !== 0) { - parts.push(ByteVector.getTextDelimiter(StringType.UTF16BE)); - } - - if (fields[i]) { - parts.push(ByteVector.fromString(fields[i], StringType.UTF16BE)); - } - } - - return parts; - } - private testWithSettings(settings: PartialSettings, action: () => void) { // Setup const originalUseNonStandardV2V3GenreSeparators = Id3v2Settings.useNonStandardV2V3GenreSeparators; diff --git a/test-unit/id3v2/id3v2TagTests.ts b/test-unit/id3v2/id3v2TagTests.ts index 036bd11a..e8ebdf48 100644 --- a/test-unit/id3v2/id3v2TagTests.ts +++ b/test-unit/id3v2/id3v2TagTests.ts @@ -2,6 +2,7 @@ import * as TypeMoq from "typemoq"; import {params, suite, test} from "@testdeck/mocha"; import {assert} from "chai"; +import AttachmentFrame from "../../src/id3v2/frames/attachmentFrame"; import CommentsFrame from "../../src/id3v2/frames/commentsFrame"; import GenreFrame from "../../src/id3v2/frames/genreFrame"; import Id3v2Settings from "../../src/id3v2/id3v2Settings"; @@ -26,7 +27,6 @@ import {Id3v2TagHeader, Id3v2TagHeaderFlags} from "../../src/id3v2/id3v2TagHeade import {IPicture} from "../../src/picture"; import {TagTypes} from "../../src/tag"; import {Testers} from "../utilities/testers"; -import AttachmentFrame from "../../src/id3v2/frames/attachmentFrame"; const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: number): ByteVector => { return ByteVector.concatenate( @@ -81,19 +81,14 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public fromData_v4Tag() { // Arrange - const frame1 = PlayCountFrame.fromEmpty() - .render(4); - const frame2 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.fromString("bar", StringType.UTF8)) - .render(4); - const emptyFrame = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.empty()) - .render(4); - const data = ByteVector.concatenate( - getTestTagHeader(4, Id3v2TagHeaderFlags.None, frame1.length + frame2.length + emptyFrame.length + 5), - frame1, - frame2, - emptyFrame, - 0x00, 0x00, 0x00, 0x00, 0x00 // Padding at end of tag + const frameBytes = ByteVector.concatenate( + PlayCountFrame.fromFields().render(4), + UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromUint(123)).render(4), + UnknownFrame.fromFields(FrameIdentifiers.RVRB, ByteVector.empty()).render(4), // Empty frame + ByteVector.fromSize(5) // Padding ); + const tagHeader = getTestTagHeader(4, Id3v2TagHeaderFlags.None, frameBytes.length); + const data = ByteVector.concatenate(tagHeader, frameBytes); // Act const tag = Id3v2Tag.fromData(data); @@ -102,33 +97,28 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: assert.isOk(tag); assert.strictEqual(tag.tagTypes, TagTypes.Id3v2); - let frame1Found = false; - let frame2Found = false; - let emptyFrameFound = false; + let playCountFrames = 0; + let ufidFrames = 0; for (const f of tag.frames) { if (f instanceof PlayCountFrame) { - assert.isFalse(frame1Found); - frame1Found = true; + playCountFrames++; } else if (f instanceof UniqueFileIdentifierFrame) { - assert.isFalse(frame2Found); - frame2Found = true; + ufidFrames++; } else if (f instanceof UnknownFrame) { - assert.isFalse(emptyFrameFound); - emptyFrameFound = true; + assert.fail("Empty frame found"); } else { - assert.fail(f, undefined, "Unexpected frame found"); + assert.fail("Unexpected frame found"); } } - assert.isTrue(frame1Found); - assert.isTrue(frame2Found); - assert.isFalse(emptyFrameFound); + assert.strictEqual(playCountFrames, 1); + assert.strictEqual(ufidFrames, 1); } @test public fromData_extendedHeader() { // Arrange - const frame1 = PlayCountFrame.fromEmpty().render(4); + const frame1 = PlayCountFrame.fromFields().render(4); const data = ByteVector.concatenate( getTestTagHeader(4, Id3v2TagHeaderFlags.ExtendedHeader, frame1.length + 10), SyncData.fromUint(10), @@ -153,7 +143,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public fromData_needsResync() { // Arrange - const frame1 = PlayCountFrame.fromEmpty().render(4); + const frame1 = PlayCountFrame.fromFields(BigInt(123)).render(4); const data = ByteVector.concatenate( getTestTagHeader(3, Id3v2TagHeaderFlags.Unsynchronization, frame1.length), frame1 @@ -211,52 +201,42 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: assert.strictEqual(output.tagTypes, TagTypes.Id3v2); } - @test - public fromFileStart_v4Tag() { - const frame1 = PlayCountFrame.fromEmpty() - .render(4); - const frame2 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.fromString("bar", StringType.UTF8)) - .render(4); - const emptyFrame = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.empty()) - .render(4); - const data = ByteVector.concatenate( - 0x00, 0x00, - getTestTagHeader(4, Id3v2TagHeaderFlags.None, frame1.length + frame2.length + emptyFrame.length + 5), - frame1, - frame2, - emptyFrame, - 0x00, 0x00, 0x00, 0x00, 0x00 // Padding at end of tag + @params(0, "without_offset") + @params(2, "with_offset") + public fromFileStart_v4Tag(offset: number) { + const frameBytes = ByteVector.concatenate( + PlayCountFrame.fromFields(BigInt(123)).render(4), + UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromUint(123)).render(4), + UnknownFrame.fromFields(FrameIdentifiers.RVRB).render(4), // Empty frame + ByteVector.fromSize(5) // Padding ); + const headerBytes = getTestTagHeader(4, Id3v2TagHeaderFlags.None, frameBytes.length); + const data = ByteVector.concatenate(ByteVector.fromSize(offset), headerBytes, frameBytes); const file = TestFile.getFile(data); // Act - const tag = Id3v2Tag.fromFileStart(file, 2, ReadStyle.None); + const tag = Id3v2Tag.fromFileStart(file, offset, ReadStyle.None); // Assert assert.isOk(tag); assert.strictEqual(tag.tagTypes, TagTypes.Id3v2); - let frame1Found = false; - let frame2Found = false; - let emptyFrameFound = false; + let playCountFrames = 0; + let ufidFrames = 0; for (const f of tag.frames) { if (f instanceof PlayCountFrame) { - assert.isFalse(frame1Found); - frame1Found = true; + playCountFrames++; } else if (f instanceof UniqueFileIdentifierFrame) { - assert.isFalse(frame2Found); - frame2Found = true; + ufidFrames++; } else if (f instanceof UnknownFrame) { - assert.isFalse(emptyFrameFound); - emptyFrameFound = true; + assert.fail("Empty frame found"); } else { - assert.fail(f, undefined, "Unexpected frame found"); + assert.fail("Unexpected frame found"); } } - assert.isTrue(frame1Found); - assert.isTrue(frame2Found); - assert.isFalse(emptyFrameFound); + assert.strictEqual(playCountFrames, 1); + assert.strictEqual(ufidFrames, 1); } } @@ -282,19 +262,23 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const set = (v: boolean) => { tag.isCompilation = v; }; // Act / Assert + // 1) No frame => not a compilation assert.isFalse(tag.isCompilation); + // 2) Set to true => creates a compilation frame PropertyTests.propertyRoundTrip(set, get, true); - assert.strictEqual(tag.frames.length, 1); - assert.instanceOf(tag.frames[0], TextInformationFrame); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TCMP); - assert.deepStrictEqual(( tag.frames[0]).text, ["1"]); + let compilationFrames = TextInformationFrame.filterFrames(tag.frames, FrameIdentifiers.TCMP); + assert.strictEqual(compilationFrames.length, 1); + assert.deepStrictEqual(compilationFrames[0].text, ["1"]); - PropertyTests.propertyRoundTrip(set, get, false); - assert.strictEqual(tag.frames.length, 0); - - tag.setTextFrame(FrameIdentifiers.TCMP, "0"); + // 3) Frame is not "1" => not a compilation + compilationFrames[0].text = ["0"]; assert.strictEqual(tag.isCompilation, false); + + // 4) Set is compilator to false => frame is deleted + PropertyTests.propertyRoundTrip(set, get, false); + compilationFrames = TextInformationFrame.filterFrames(tag.frames, FrameIdentifiers.TCMP); + assert.strictEqual(compilationFrames.length, 0); } @test @@ -398,10 +382,10 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: // Act / Assert assert.deepStrictEqual(tag.performersRole, []); - const tmclFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TMCL); const tmclText = ["saxophone", "alice,bob", "flugelhorn", "alice"]; - tmclFrame.text = tmclText; + const tmclFrame = TextInformationFrame.fromFields(FrameIdentifiers.TMCL, tmclText); tag.frames.push(tmclFrame); + tag.performers = ["alice", "bob", "malory"]; const expected = ["saxophone; flugelhorn", "saxophone", undefined]; assert.deepStrictEqual(tag.performersRole, expected); @@ -489,12 +473,10 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public comment_multipleFrames_picksBestLanguage() { // Arrange + const frame1 = CommentsFrame.fromFields("foo", "bar", "jpn"); + const frame2 = CommentsFrame.fromFields("fux", "bux", "eng"); const tag = Id3v2Tag.fromEmpty(); - const frame1 = CommentsFrame.fromDescription("", "jpn"); - frame1.text = "foo"; tag.addFrame(frame1); - const frame2 = CommentsFrame.fromDescription("", "eng"); - frame2.text = "bar"; tag.addFrame(frame2); const initialLanguage = Id3v2Tag.language; @@ -504,21 +486,19 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const output = tag.comment; // Assert - assert.strictEqual(output, "bar"); + assert.strictEqual(output, "bux"); } finally { Id3v2Tag.language = initialLanguage; } } @test - public comment_setToFalsy_removesFrames() { + public comment_setToFalsy_removesAllFrames() { // Arrange + const frame1 = CommentsFrame.fromFields("foo", "bar", "jpn"); + const frame2 = CommentsFrame.fromFields("fux", "bux", "eng"); const tag = Id3v2Tag.fromEmpty(); - const frame1 = CommentsFrame.fromDescription("", "jpn"); - frame1.text = "foo"; tag.addFrame(frame1); - const frame2 = CommentsFrame.fromDescription("", "eng"); - frame2.text = "bar"; tag.addFrame(frame2); const initialLanguage = Id3v2Tag.language; @@ -538,12 +518,10 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public comment_setToTruthy_setsLanguageFrame() { // Arrange + const frame1 = CommentsFrame.fromFields("foo", "bar", "jpn"); + const frame2 = CommentsFrame.fromFields("fux", "bux", "eng"); const tag = Id3v2Tag.fromEmpty(); - const frame1 = CommentsFrame.fromDescription("", "jpn"); - frame1.text = "foo"; tag.addFrame(frame1); - const frame2 = CommentsFrame.fromDescription("", "eng"); - frame2.text = "bar"; tag.addFrame(frame2); const initialLanguage = Id3v2Tag.language; @@ -556,7 +534,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: assert.strictEqual(tag.comment, "qux"); assert.strictEqual(tag.frames.length, 2); assert.strictEqual(frame2.text, "qux"); - assert.strictEqual(frame1.text, "foo"); + assert.strictEqual(frame1.text, "bar"); } finally { Id3v2Tag.language = initialLanguage; } @@ -600,9 +578,8 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public genres_withFrame() { // Arrange + const frame = GenreFrame.fromFields(["Classical", "foo"]); const tag = Id3v2Tag.fromEmpty(); - const frame = GenreFrame.fromEncoding(); - frame.text = ["Classical", "foo"]; tag.addFrame(frame); // Act @@ -630,9 +607,8 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public genres_setValueWithFrame() { // Arrange + const frame = GenreFrame.fromFields(["qux"]); const tag = Id3v2Tag.fromEmpty(); - const frame = GenreFrame.fromEncoding(); - frame.text = ["qux"]; tag.addFrame(frame); // Act @@ -687,13 +663,11 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: // Act / Assert assert.strictEqual(tag.year, 0); - const tdrcFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TDRC); - tdrcFrame.text = ["1234-04-25"]; + const tdrcFrame = TextInformationFrame.fromFields(FrameIdentifiers.TDRC, ["1234-04-25"]); tag.frames.push(tdrcFrame); assert.strictEqual(tag.year, 1234); - const tyerFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TYER); - tyerFrame.text = ["2345"]; + const tyerFrame = TextInformationFrame.fromFields(FrameIdentifiers.TYER, ["2345"]); tag.frames.push(tyerFrame); assert.strictEqual(tag.year, 1234); @@ -716,8 +690,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: // Act / Assert assert.strictEqual(tag.year, 0); - const tyerFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TYER); - tyerFrame.text = ["1234"]; + const tyerFrame = TextInformationFrame.fromFields(FrameIdentifiers.TYER, ["1234"]); tag.frames.push(tyerFrame); assert.strictEqual(tag.year, 1234); @@ -1071,11 +1044,10 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public lyrics_multipleFrames() { // Arrange - const frame1 = UnsynchronizedLyricsFrame.fromData("foo", "jpn"); // 0 Score - const frame2 = UnsynchronizedLyricsFrame.fromData("", "jpn"); // 1 Score - const frame3 = UnsynchronizedLyricsFrame.fromData("foo", "eng"); // 2 Score - const frame4 = UnsynchronizedLyricsFrame.fromData("", "eng"); // 3 Score - frame4.text = "foobarbaz"; + const frame1 = UnsynchronizedLyricsFrame.fromFields("foo", undefined, "jpn"); // 0 Score + const frame2 = UnsynchronizedLyricsFrame.fromFields("", undefined, "jpn"); // 1 Score + const frame3 = UnsynchronizedLyricsFrame.fromFields("foo", undefined, "eng"); // 2 Score + const frame4 = UnsynchronizedLyricsFrame.fromFields("", "foobarbaz", "eng"); // 3 Score const tag = Id3v2Tag.fromEmpty(); tag.frames.push(frame1, frame2, frame3, frame4); @@ -1536,8 +1508,8 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: public clear() { // Arrange const tag = Id3v2Tag.fromEmpty(); - tag.frames.push(PlayCountFrame.fromEmpty()); - tag.frames.push(PlayCountFrame.fromEmpty()); + tag.frames.push(PlayCountFrame.fromFields()); + tag.frames.push(PlayCountFrame.fromFields()); // Act tag.clear(); @@ -1561,15 +1533,13 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public copyTo_noOverwrite() { // Arrange + const sFrame1 = PlayCountFrame.fromFields(BigInt(123)); + const sFrame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo", "bar"]); const source = Id3v2Tag.fromEmpty(); - const sFrame1 = PlayCountFrame.fromEmpty(); - sFrame1.playCount = BigInt(123); - const sFrame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - sFrame2.text = ["foo", "bar"]; source.frames.push(sFrame1, sFrame2); + const dFrame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); const dest = Id3v2Tag.fromEmpty(); - const dFrame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); dest.frames.push(dFrame1); // Act @@ -1591,15 +1561,13 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public copyTo_overwrite() { // Arrange + const sFrame1 = PlayCountFrame.fromFields(BigInt(123)); + const sFrame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo", "bar"]); const source = Id3v2Tag.fromEmpty(); - const sFrame1 = PlayCountFrame.fromEmpty(); - sFrame1.playCount = BigInt(123); - const sFrame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - sFrame2.text = ["foo", "bar"]; source.frames.push(sFrame1, sFrame2); + const dFrame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); const dest = Id3v2Tag.fromEmpty(); - const dFrame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); dest.frames.push(dFrame1); // Act @@ -1619,84 +1587,6 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const dPcnt = PlayCountFrame.filterFrames(dest.frames)[0]; assert.strictEqual(dPcnt.playCount, sFrame1.playCount); } - @test - public getTextAsString_invalidIdentity() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - assert.throws(() => { tag.getTextAsString(undefined); }); - assert.throws(() => { tag.getTextAsString(null); }); - } - - @test - public getTextAsString_urlFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame2.text = "foo"; - const frame3 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame3.text = "bar"; - tag.frames.push(frame1, frame2, frame3); - - // Act - const result = tag.getTextAsString(FrameIdentifiers.WCOM); - - // Assert - assert.strictEqual(result, "foo"); - } - - @test - public getTextAsString_textFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame2.text = ["foo"]; - const frame3 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame3.text = ["bar"]; - tag.frames.push(frame1, frame2, frame3); - - // Act - const result = tag.getTextAsString(FrameIdentifiers.TCOM); - - // Assert - assert.strictEqual(result, "foo"); - } - - @test - public getTextAsString_genreFrame() { - // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame3 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame4 = GenreFrame.fromEncoding(StringType.Latin1); - frame4.text = ["foo"] - - const tag = Id3v2Tag.fromEmpty(); - tag.frames.push(frame1, frame2, frame3, frame4); - - // Act - const result = tag.getTextAsString(FrameIdentifiers.TCON); - - // Assert - assert.strictEqual(result, "foo"); - } - - @test - public getTextAsString_noMatches() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - tag.frames.push(frame1); - - // Act - const result = tag.getTextAsString(FrameIdentifiers.TCOM); - - // Assert - assert.isUndefined(result); - } @test public removeFrame_invalidFrame() { @@ -1711,8 +1601,8 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public removeFrame_frameExists() { // Arrange + const frame = PlayCountFrame.fromFields(); const tag = Id3v2Tag.fromEmpty(); - const frame = PlayCountFrame.fromEmpty(); tag.frames.push(frame); // Act @@ -1725,11 +1615,12 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public removeFrame_frameDoesNotExist() { // Arrange + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); const tag = Id3v2Tag.fromEmpty(); - const frame1 = PlayCountFrame.fromEmpty(); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); tag.frames.push(frame2); + const frame1 = PlayCountFrame.fromFields(); + // Act tag.removeFrame(frame1); @@ -1744,25 +1635,21 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const tag = Id3v2Tag.fromEmpty(); tag.version = 4; - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame1Data = frame1.render(4); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCON); - const frame2Data = frame2.render(4); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCON); tag.frames.push(frame1, frame2); // Act const output = tag.render(); // Assert - const header = new Id3v2TagHeader(); - header.tagSize = 1024 + frame1Data.length + frame2Data.length; - header.majorVersion = 4; - const expected = ByteVector.concatenate( - header.render(), - frame1Data, - frame2Data, + const frameBytes = ByteVector.concatenate( + frame1.render(4), + frame2.render(4), ByteVector.fromSize(1024, 0x00) ); + const header = new Id3v2TagHeader(4, 0, Id3v2TagHeaderFlags.None, frameBytes.length); + const expected = ByteVector.concatenate(header.render(), frameBytes); Testers.bvEqual(output, expected); } @@ -1774,24 +1661,22 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: tag.version = 4; tag.flags = Id3v2TagHeaderFlags.FooterPresent; - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame1Data = frame1.render(4); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCON); - const frame2Data = frame2.render(4); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCON); tag.frames.push(frame1, frame2); // Act const output = tag.render(); // Assert - const header = new Id3v2TagHeader(); - header.tagSize = frame1Data.length + frame2Data.length; - header.majorVersion = 4; - header.flags = Id3v2TagHeaderFlags.FooterPresent; + const frameBytes = ByteVector.concatenate( + frame1.render(4), + frame2.render(4) + ); + const header = new Id3v2TagHeader(4, 0, Id3v2TagHeaderFlags.FooterPresent, frameBytes.length); const expected = ByteVector.concatenate( header.render(), - frame1Data, - frame2Data, + frameBytes, Id3v2TagFooter.fromHeader(header).render() ); @@ -1801,34 +1686,25 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public render_v4_unsyncAtFrameLevel() { // Arrange + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCON); + const tag = Id3v2Tag.fromEmpty(); tag.version = 4; tag.flags = Id3v2TagHeaderFlags.Unsynchronization; - - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCON); tag.frames.push(frame1, frame2); // Act const output = tag.render(); // Assert - frame1.flags |= Id3v2FrameFlags.Unsynchronized; - frame2.flags |= Id3v2FrameFlags.Unsynchronized; - const frame1Data = frame1.render(4); - const frame2Data = frame2.render(4); - - const header = new Id3v2TagHeader(); - header.tagSize = 1024 + frame1Data.length + frame2Data.length; - header.flags = Id3v2TagHeaderFlags.Unsynchronization; - header.majorVersion = 4; - - const expected = ByteVector.concatenate( - header.render(), - frame1Data, - frame2Data, - ByteVector.fromSize(1024, 0x00) + const frameBytes = ByteVector.concatenate( + frame1.render(4), + frame2.render(4), + ByteVector.fromSize(1024) ); + const header = new Id3v2TagHeader(4, 0, Id3v2TagHeaderFlags.Unsynchronization, frameBytes.length); + const expected = ByteVector.concatenate(header.render(), frameBytes); Testers.bvEqual(output, expected); } @@ -1836,36 +1712,25 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: @test public render_v3_unsyncAtTagLevel() { // Arrange + const frame1 = PrivateFrame.fromFields("foobarbaz", ByteVector.fromByteArray([0xAA, 0xFF, 0x00, 0xAA])); + const frame2 = PrivateFrame.fromFields("fuxbuxqux", ByteVector.fromByteArray([0xAA, 0x12, 0x34, 0xAA])); + const tag = Id3v2Tag.fromEmpty(); tag.version = 3; tag.flags = Id3v2TagHeaderFlags.Unsynchronization; - - const frame1 = PrivateFrame.fromOwner("foobarbaz"); - frame1.privateData = ByteVector.fromByteArray([0xAA, 0xFF, 0x00, 0xAA]); - const frame2 = PrivateFrame.fromOwner("fuxbuxqux"); - frame2.privateData = ByteVector.fromByteArray([0xAA, 0x12, 0x34, 0xAA]); tag.frames.push(frame1, frame2); // Act const output = tag.render(); // Assert - let frameData = ByteVector.concatenate( + const frameData = SyncData.unsyncByteVector(ByteVector.concatenate( frame1.render(3), - frame2.render(3) - ); - frameData = SyncData.unsyncByteVector(frameData); - - const header = new Id3v2TagHeader(); - header.flags = Id3v2TagHeaderFlags.Unsynchronization; - header.tagSize = 1024 + frameData.length; - header.majorVersion = 3; - - const expected = ByteVector.concatenate( - header.render(), - frameData, - ByteVector.fromSize(1024, 0x00) - ); + frame2.render(3), + ByteVector.fromSize(1024) + )); + const header = new Id3v2TagHeader(3, 0, Id3v2TagHeaderFlags.Unsynchronization, frameData.length); + const expected = ByteVector.concatenate(header.render(), frameData); Testers.bvEqual(output, expected); } @@ -1879,7 +1744,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const originalSetting = Id3v2Settings.strictFrameForVersion; Id3v2Settings.strictFrameForVersion = true; - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TYER); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TYER); frame1.text = ["foo"]; tag.frames.push(frame1); @@ -1900,7 +1765,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: const originalSetting = Id3v2Settings.strictFrameForVersion; Id3v2Settings.strictFrameForVersion = false; - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TYER); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TYER); tag.frames.push(frame1); try { @@ -1920,7 +1785,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: public replaceFrame_invalidFrames() { // Arrange const tag = Id3v2Tag.fromEmpty(); - const frame = PlayCountFrame.fromEmpty(); + const frame = PlayCountFrame.fromFields(BigInt(123)); // Act / Assert assert.throws(() => { tag.replaceFrame(undefined, frame); }); @@ -1933,7 +1798,7 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: public replaceFrame_sameFrame() { // Arrange const tag = Id3v2Tag.fromEmpty(); - const frame = PlayCountFrame.fromEmpty(); + const frame = PlayCountFrame.fromFields(BigInt(123)); // Act tag.replaceFrame(frame, frame); @@ -1946,8 +1811,8 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: public replaceFrame_frameExists() { // Arrange const tag = Id3v2Tag.fromEmpty(); - const oldFrame = PlayCountFrame.fromEmpty(); - const newFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); + const oldFrame = PlayCountFrame.fromFields(BigInt(123)); + const newFrame = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo"]); tag.frames.push(oldFrame); @@ -1962,9 +1827,9 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: public replaceFrame_frameDoesNotExist() { // Arrange const tag = Id3v2Tag.fromEmpty(); - const oldFrame = PlayCountFrame.fromEmpty(); - const newFrame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const otherFrame = PlayCountFrame.fromEmpty(); + const oldFrame = PlayCountFrame.fromFields(BigInt(123)); + const newFrame = TextInformationFrame.fromFields(FrameIdentifiers.TCOM, ["foo"]); + const otherFrame = PlayCountFrame.fromFields(BigInt(234)); tag.frames.push(otherFrame); @@ -1974,225 +1839,4 @@ const getTestTagHeader = (version: number, flags: Id3v2TagHeaderFlags, tagSize: // Assert assert.sameMembers(tag.frames, [otherFrame, newFrame]); } - - @test - public setNumberFrame_invalidArgs() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const identifier = FrameIdentifiers.TCOM; - - // Act / Assert - assert.throws(() => { tag.setNumberFrame(undefined, 1, 1, 1); }); - assert.throws(() => { tag.setNumberFrame(null, 1, 1, 1); }); - - assert.throws(() => { tag.setNumberFrame(identifier, -1, 1, 1); }); - assert.throws(() => { tag.setNumberFrame(identifier, 1.23, 1, 1); }); - assert.throws(() => { tag.setNumberFrame(identifier, Number.MAX_SAFE_INTEGER + 1, 1, 1); }); - - assert.throws(() => { tag.setNumberFrame(identifier, 1, -1, 1); }); - assert.throws(() => { tag.setNumberFrame(identifier, 1, 1.23, 1); }); - assert.throws(() => { tag.setNumberFrame(identifier, 1, Number.MAX_SAFE_INTEGER + 1, 1); }); - - assert.throws(() => { tag.setNumberFrame(identifier, 1, 1, -1); }); - assert.throws(() => { tag.setNumberFrame(identifier, 1, 1, 1.23); }); - assert.throws(() => { tag.setNumberFrame(identifier, 1, 1, 0x100); }); - } - - @test - public setNumberFrame_removesFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TRCK); - tag.frames.push(frame); - - // Act - tag.setNumberFrame(FrameIdentifiers.TRCK, 0, 0, 1); - - // Assert - assert.isEmpty(tag.frames); - } - - @test - public setNumberFrame_noDenominator() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act - tag.setNumberFrame(FrameIdentifiers.TRCK, 123, 0, 4); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TRCK); - assert.deepStrictEqual(( tag.frames[0]).text, ["0123"]); - } - - @test - public setNumberFrame_withDenominator() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act - tag.setNumberFrame(FrameIdentifiers.TRCK, 123, 234, 4); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TRCK); - assert.deepStrictEqual(( tag.frames[0]).text, ["0123/234"]); - } - - @test - public setTextFrame_falsyIdentifier() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - Testers.testTruthy((v: FrameIdentifier) => tag.setTextFrame(v, "foo")); - } - - @params(FrameIdentifiers.WXXX, "WXXX") - @params(FrameIdentifiers.RVRB, "RVRB") - @params(FrameIdentifiers.SYLT, "SYLT") - public setUrlFrame_notTextFrame(identifier: FrameIdentifier) { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - assert.throws(() => tag.setTextFrame(identifier, "")); - } - - @test - public setTextFrame_userTextFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - assert.throws(() => tag.setTextFrame(FrameIdentifiers.TXXX, "foo")); - } - - @test - public setTextFrame_falsyValues_removesFrame() { - // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame1.text = ["foo"]; - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame2.text = ["bar"]; - - const tag = Id3v2Tag.fromEmpty(); - tag.frames.push(frame1, frame2); - - // Act - tag.setTextFrame(FrameIdentifiers.TCOM, undefined, null, ""); - - // Assert - assert.strictEqual(tag.frames.length, 0); - } - - @test - public setTextFrame_textFrameNoMatch() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act - tag.setTextFrame(FrameIdentifiers.TCOM, "foo", "bar"); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TCOM); - assert.deepStrictEqual(( tag.frames[0]).text, ["foo", "bar"]); - } - - @test - public setTextFrame_textFrameWithMatch() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - frame.text = ["fux", "qux"]; - - // Act - tag.setTextFrame(FrameIdentifiers.TCOM, "foo", "bar"); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.TCOM); - assert.deepStrictEqual(( tag.frames[0]).text, ["foo", "bar"]); - } - - @test - public setUrlFrame_falsyIdentifier() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - Testers.testTruthy((ident: FrameIdentifier) => tag.setUrlFrame(ident, "")); - } - - @params(FrameIdentifiers.TXXX, "TXXX") - @params(FrameIdentifiers.RVRB, "RVRB") - @params(FrameIdentifiers.SYLT, "SYLT") - public setUrlFrame_notUrlFrame(identifier: FrameIdentifier) { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - assert.throws(() => tag.setUrlFrame(identifier, "")); - } - - @test - public setUrlFrame_userUrlFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act / Assert - assert.throws(() => tag.setUrlFrame(FrameIdentifiers.WXXX, "foo")); - } - - @params("", "empty string") - @params(null, "null") - @params(undefined, "undefined") - public setUrlFrame_falsyValue_removesFrame(text: string) { - // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame1.text = "foo"; - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame2.text = "bar"; - - const tag = Id3v2Tag.fromEmpty(); - tag.frames.push(frame1, frame2); - - // Act - tag.setUrlFrame(FrameIdentifiers.WCOM, text); - - // Assert - assert.isTrue(tag.isEmpty); - } - - @test - public setUrlFrame_withoutMatchingFrames_addsFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - - // Act - tag.setUrlFrame(FrameIdentifiers.WCOM, "foo"); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.WCOM); - assert.strictEqual((tag.frames[0]).text, "foo"); - } - - @test - public setUrlFrame_withMatchingFrames_updatesFrame() { - // Arrange - const tag = Id3v2Tag.fromEmpty(); - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = "fux"; - - // Act - tag.setUrlFrame(FrameIdentifiers.WCOM, "foo"); - - // Assert - assert.strictEqual(tag.frames.length, 1); - assert.strictEqual(tag.frames[0].frameId, FrameIdentifiers.WCOM); - assert.strictEqual((tag.frames[0]).text, "foo"); - } } diff --git a/test-unit/id3v2/musicCdIdentifierFrameTests.ts b/test-unit/id3v2/musicCdIdentifierFrameTests.ts index 4f0b36e1..28318024 100644 --- a/test-unit/id3v2/musicCdIdentifierFrameTests.ts +++ b/test-unit/id3v2/musicCdIdentifierFrameTests.ts @@ -16,18 +16,6 @@ import {Testers} from "../utilities/testers"; return MusicCdIdentifierFrame.fromFieldBytes; } - @test - public fromData_withData_frameHasData() { - // Arrange - const data = ByteVector.fromString("fux qux quxx", StringType.UTF8); - - // Act - const frame = MusicCdIdentifierFrame.fromData(data); - - // Assert - Id3v2_MusicCdIdentifierFrameTests.assertFrame(frame, data); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -43,11 +31,32 @@ import {Testers} from "../utilities/testers"; Id3v2_MusicCdIdentifierFrameTests.assertFrame(frame, ByteVector.fromString("foo bar baz", StringType.Latin1)); } + @test + public fromFields_noData() { + // Act + const frame = MusicCdIdentifierFrame.fromFields(); + + // Assert + Id3v2_MusicCdIdentifierFrameTests.assertFrame(frame, ByteVector.empty()); + } + + @test + public fromFields_withData() { + // Arrange + const data = ByteVector.fromString("fux qux quxx", StringType.UTF8); + + // Act + const frame = MusicCdIdentifierFrame.fromFields(data); + + // Assert + Id3v2_MusicCdIdentifierFrameTests.assertFrame(frame, data); + } + @test public data() { // Arrange const value = ByteVector.fromByteArray([0x01, 0x02, 0x03, 0x04]); - const frame = MusicCdIdentifierFrame.fromData(value); + const frame = MusicCdIdentifierFrame.fromFields(value); // Act / Assert PropertyTests.propertyRoundTrip((v) => { frame.data = v; }, () => frame.data, value); @@ -91,8 +100,8 @@ import {Testers} from "../utilities/testers"; @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -106,8 +115,8 @@ import {Testers} from "../utilities/testers"; @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = MusicCdIdentifierFrame.fromData(ByteVector.empty()); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = MusicCdIdentifierFrame.fromFields(ByteVector.empty()); const frames = [frame1, frame2]; // Act @@ -121,9 +130,9 @@ import {Testers} from "../utilities/testers"; @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = MusicCdIdentifierFrame.fromData(ByteVector.empty()); - const frame3 = MusicCdIdentifierFrame.fromData(ByteVector.empty()); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = MusicCdIdentifierFrame.fromFields(ByteVector.empty()); + const frame3 = MusicCdIdentifierFrame.fromFields(ByteVector.empty()); const frames = [frame1, frame2, frame3]; @@ -138,8 +147,8 @@ import {Testers} from "../utilities/testers"; @test public filterFrames_allMatches() { // Arrange - const frame1 = MusicCdIdentifierFrame.fromData(ByteVector.empty()); - const frame2 = MusicCdIdentifierFrame.fromData(ByteVector.empty()); + const frame1 = MusicCdIdentifierFrame.fromFields(ByteVector.empty()); + const frame2 = MusicCdIdentifierFrame.fromFields(ByteVector.empty()); const frames = [frame1, frame2]; // Act diff --git a/test-unit/id3v2/playCountFrameTests.ts b/test-unit/id3v2/playCountFrameTests.ts index 40824a94..74250b62 100644 --- a/test-unit/id3v2/playCountFrameTests.ts +++ b/test-unit/id3v2/playCountFrameTests.ts @@ -24,15 +24,6 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { return PlayCountFrame.fromFieldBytes; } - @test - public fromEmpty() { - // Act - const frame = PlayCountFrame.fromEmpty(); - - // Assert - assertFrame(frame, BigInt(0)); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -101,13 +92,30 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { // Assert assertFrame(frame, BigInt("72623859790382856")); } + + @test + public fromFields_noParams() { + // Act + const frame = PlayCountFrame.fromFields(); + + // Assert + assertFrame(frame, BigInt(0)); + } + @test + public fromFields_withPlayCount() { + // Act + const frame = PlayCountFrame.fromFields(BigInt(12345)); + + // Assert + assertFrame(frame, BigInt(12345)); + } } @suite class Id3v2_PlayCountFrame_PropertyTests { @test public playCount() { // Arrange - const frame = PlayCountFrame.fromEmpty(); + const frame = PlayCountFrame.fromFields(); const set = (v: bigint) => { frame.playCount = v; }; const get = () => frame.playCount; @@ -123,8 +131,7 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @test public clone() { // Arrange - const frame = PlayCountFrame.fromEmpty(); - frame.playCount = BigInt(123); + const frame = PlayCountFrame.fromFields(BigInt(123)); // Act const output = frame.clone(); @@ -155,8 +162,8 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -170,8 +177,8 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PlayCountFrame.fromEmpty(); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PlayCountFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -185,9 +192,9 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PlayCountFrame.fromEmpty(); - const frame3 = PlayCountFrame.fromEmpty(); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PlayCountFrame.fromFields(); + const frame3 = PlayCountFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -202,8 +209,8 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @test public filterFrames_allMatches() { // Arrange - const frame1 = PlayCountFrame.fromEmpty(); - const frame2 = PlayCountFrame.fromEmpty(); + const frame1 = PlayCountFrame.fromFields(); + const frame2 = PlayCountFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -219,8 +226,7 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @params(4, "v4") public render_fourBytePlayCount(version: number) { // Arrange - const frame = PlayCountFrame.fromEmpty(); - frame.playCount = BigInt(1234); + const frame = PlayCountFrame.fromFields(BigInt(1234)); // Act const output = frame.render(version); @@ -239,8 +245,7 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @params(4, "v4") public render_sixBytePlayCount(version: number) { // Arrange - const frame = PlayCountFrame.fromEmpty(); - frame.playCount = BigInt("1108152157446"); + const frame = PlayCountFrame.fromFields(BigInt("1108152157446")); // Act const output = frame.render(version); @@ -259,8 +264,7 @@ const assertFrame = (frame: PlayCountFrame, p: bigint) => { @params(4, "v4") public render_eightBytePlayCount(version: number) { // Arrange - const frame = PlayCountFrame.fromEmpty(); - frame.playCount = BigInt("72623859790382856"); + const frame = PlayCountFrame.fromFields(BigInt("72623859790382856")); // Act const output = frame.render(version); diff --git a/test-unit/id3v2/popularimeterFrameTests.ts b/test-unit/id3v2/popularimeterFrameTests.ts index 509abde1..4c1fd6db 100644 --- a/test-unit/id3v2/popularimeterFrameTests.ts +++ b/test-unit/id3v2/popularimeterFrameTests.ts @@ -26,15 +26,6 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) return PopularimeterFrame.fromFieldBytes; } - @test - public fromUser() { - // Act - const frame = PopularimeterFrame.fromUser("fux"); - - // Assert - assertFrame(frame, "fux", undefined, 0); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -154,13 +145,49 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) // Assert assertFrame(frame, "foo@example.com", BigInt("72623859790382856"), 0xAB); } + + @test + public fromFields_noParams() { + // Act + const frame = PopularimeterFrame.fromFields(); + + // Assert + assertFrame(frame, "", undefined, 0); + } + + @test + public fromFields_withOwner() { + // Act + const frame = PopularimeterFrame.fromFields("foo"); + + // Assert + assertFrame(frame, "foo", undefined, 0); + } + + @test + public fromFields_withOwnerRating() { + // Act + const frame = PopularimeterFrame.fromFields("foo", 123); + + // Assert + assertFrame(frame, "foo", undefined, 123); + } + + @test + public fromFields_withOwnerRatingPlayCount() { + // Act + const frame = PopularimeterFrame.fromFields("foo", 123, BigInt(45678)); + + // Assert + assertFrame(frame, "foo", BigInt(45678), 123); + } } @suite class Id3v2_PopularimeterFrame_PropertyTests { @test public playCount() { // Arrange - const frame = PopularimeterFrame.fromUser("fux"); + const frame = PopularimeterFrame.fromFields(); const set = (v: bigint) => { frame.playCount = v; }; const get = () => frame.playCount; @@ -174,7 +201,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public rating() { // Arrange - const frame = PopularimeterFrame.fromUser("fux"); + const frame = PopularimeterFrame.fromFields(); const set = (v: number) => { frame.rating = v; }; const get = () => frame.rating; @@ -188,7 +215,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public user() { // Arrange - const frame = PopularimeterFrame.fromUser("fux"); + const frame = PopularimeterFrame.fromFields(); const set = (v: string) => { frame.user = v; }; const get = () => frame.user; @@ -203,9 +230,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public clone() { // Arrange - const frame = PopularimeterFrame.fromUser("fux"); - frame.playCount = BigInt(1234); - frame.rating = 5; + const frame = PopularimeterFrame.fromFields("fux", 5, BigInt(1234)); // Act const output = frame.clone(); @@ -236,8 +261,8 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -251,8 +276,8 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PopularimeterFrame.fromUser("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PopularimeterFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -266,9 +291,9 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PopularimeterFrame.fromUser("foo"); - const frame3 = PopularimeterFrame.fromUser("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PopularimeterFrame.fromFields(); + const frame3 = PopularimeterFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -283,8 +308,8 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @test public filterFrames_allMatches() { // Arrange - const frame1 = PopularimeterFrame.fromUser("foo"); - const frame2 = PopularimeterFrame.fromUser("bar"); + const frame1 = PopularimeterFrame.fromFields(); + const frame2 = PopularimeterFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -300,8 +325,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @params(4, "v4") public render_noPlayCount(version: number) { // Arrange - const frame = PopularimeterFrame.fromUser("foo@example.com"); - frame.rating = 0xAB; + const frame = PopularimeterFrame.fromFields("foo@example.com", 0xAB); // Act const output = frame.render(version); @@ -323,9 +347,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @params(4, "v4") public render_fourBytePlayCount(version: number) { // Arrange - const frame = PopularimeterFrame.fromUser("foo@example.com"); - frame.playCount = BigInt(1234); - frame.rating = 0xAB; + const frame = PopularimeterFrame.fromFields("foo@example.com", 0xAB, BigInt(1234)); // Act const output = frame.render(version); @@ -348,9 +370,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @params(4, "v4") public render_sixBytePlayCount(version: number) { // Arrange - const frame = PopularimeterFrame.fromUser("foo@example.com"); - frame.playCount = BigInt("1108152157446"); - frame.rating = 0xAB; + const frame = PopularimeterFrame.fromFields("foo@example.com", 0xAB, BigInt("1108152157446")); // Act const output = frame.render(version); @@ -373,9 +393,7 @@ const assertFrame = (frame: PopularimeterFrame, u: string, p: bigint, r: number) @params(4, "v4") public render_eightBytePlayCount(version: number) { // Arrange - const frame = PopularimeterFrame.fromUser("foo@example.com"); - frame.playCount = BigInt("72623859790382856"); - frame.rating = 0xAB; + const frame = PopularimeterFrame.fromFields("foo@example.com", 0xAB, BigInt("72623859790382856")); // Act const output = frame.render(version); diff --git a/test-unit/id3v2/privateFrameTests.ts b/test-unit/id3v2/privateFrameTests.ts index 53d52c66..67978d9e 100644 --- a/test-unit/id3v2/privateFrameTests.ts +++ b/test-unit/id3v2/privateFrameTests.ts @@ -25,15 +25,6 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { return PrivateFrame.fromFieldBytes; } - @test - public fromOwner_validParams_returnsFrame() { - // Act - const frame = PrivateFrame.fromOwner("foo"); - - // Assert - assertFrame(frame, "foo", ByteVector.empty()); - } - @test public fromFieldBytes_tooFewBytes_throws() { // Arrange @@ -100,13 +91,43 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { // Assert assertFrame(frame, "foo", dataBytes); } + + @test + public fromFields_noParams() { + // Act + const frame = PrivateFrame.fromFields(); + + // Assert + assertFrame(frame, "", ByteVector.empty()); + } + + @test + public fromFields_withOwner() { + // Act + const frame = PrivateFrame.fromFields("foo"); + + // Assert + assertFrame(frame, "foo", ByteVector.empty()); + } + + @test + public fromFields_withOwnerData() { + // Arrange + const bytes = ByteVector.fromUint(123); + + // Act + const frame = PrivateFrame.fromFields("foo", bytes); + + // Assert + assertFrame(frame, "foo", bytes); + } } @suite class Id3v2_PrivateFrame_PropertyTests { @test public privateData() { // Arrange - const frame = PrivateFrame.fromOwner("fux"); + const frame = PrivateFrame.fromFields(); // Act / Assert PropertyTests.propertyRoundTrip( @@ -121,10 +142,10 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public clone() { // Arrange - const frame = PrivateFrame.fromOwner("fux"); + const frame = PrivateFrame.fromFields("fux", ByteVector.fromUint(1234)); // Act - const output = frame.clone(); + const output = frame.clone(); // Assert assertFrame(output, frame.owner, frame.privateData); @@ -152,8 +173,8 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -167,8 +188,8 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PrivateFrame.fromOwner("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PrivateFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -182,9 +203,9 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = PrivateFrame.fromOwner("foo"); - const frame3 = PrivateFrame.fromOwner("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = PrivateFrame.fromFields(); + const frame3 = PrivateFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -199,8 +220,8 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public filterFrames_allMatches() { // Arrange - const frame1 = PrivateFrame.fromOwner("foo"); - const frame2 = PrivateFrame.fromOwner("bar"); + const frame1 = PrivateFrame.fromFields(); + const frame2 = PrivateFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -214,7 +235,7 @@ const assertFrame = (frame: PrivateFrame, o: string, d: ByteVector) => { @test public render_v2_throws() { // Arrange - const frame = PrivateFrame.fromOwner("foo"); + const frame = PrivateFrame.fromFields(); // Act / Assert assert.throws(() => frame.render(2)); diff --git a/test-unit/id3v2/relativeVolumeFrameTests.ts b/test-unit/id3v2/relativeVolumeFrameTests.ts index 0e08fa10..4aec1bef 100644 --- a/test-unit/id3v2/relativeVolumeFrameTests.ts +++ b/test-unit/id3v2/relativeVolumeFrameTests.ts @@ -287,15 +287,6 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => return RelativeVolumeFrame.fromFieldBytes; } - @test - public fromIdentification() { - // Act - const frame = RelativeVolumeFrame.fromIdentification("foo"); - - // Assert - assertFrame(frame, [], "foo"); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -443,13 +434,49 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => cd1.volumeAdjustment = 123 / 512; assertFrame(frame, [cd1], "foobarbaz"); } + + @test + public fromFields_noParams() { + // Act + const frame = RelativeVolumeFrame.fromFields(); + + // Assert + + assertFrame(frame, [], ""); + } + + @test + public fromFields_withIdentification() { + // Act + const frame = RelativeVolumeFrame.fromFields("foo"); + + // Assert + assertFrame(frame, [], "foo"); + } + + @test + public fromFields_withIdentificationChannelData() { + // Arrange + const channelData = []; + for(let i = 0; i < 9; i++) { + const value = new ChannelData(i); + value.volumeAdjustment = i * 2 + 1; + channelData.push(value); + } + + // Act + const frame = RelativeVolumeFrame.fromFields("foo", channelData); + + // Assert + assertFrame(frame, channelData, "foo"); + } } @suite class Id3v2_RelativeVolumeFrame_MethodTests { @test public clone() { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foobarbaz"); + const frame = RelativeVolumeFrame.fromFields("foobarbaz"); frame.setPeakBits(ChannelType.Subwoofer, 32); frame.setPeakVolume(ChannelType.Subwoofer, BigInt(12345)); frame.setVolumeAdjustment(ChannelType.Subwoofer, -1.23); @@ -502,8 +529,8 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -517,8 +544,8 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = RelativeVolumeFrame.fromIdentification("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = RelativeVolumeFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -532,9 +559,9 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = RelativeVolumeFrame.fromIdentification("foo"); - const frame3 = RelativeVolumeFrame.fromIdentification("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = RelativeVolumeFrame.fromFields(); + const frame3 = RelativeVolumeFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -549,8 +576,8 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public filterFrames_allMatches() { // Arrange - const frame1 = RelativeVolumeFrame.fromIdentification("foo"); - const frame2 = RelativeVolumeFrame.fromIdentification("bar"); + const frame1 = RelativeVolumeFrame.fromFields(); + const frame2 = RelativeVolumeFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -564,7 +591,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public peakBits() { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foo"); + const frame = RelativeVolumeFrame.fromFields(); // Act / Assert PropertyTests.propertyRoundTrip( @@ -577,7 +604,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public peakVolume() { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foo"); + const frame = RelativeVolumeFrame.fromFields(); frame.setPeakBits(ChannelType.Subwoofer, 16); // Act / Assert @@ -591,7 +618,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @test public volumeAdjustment() { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foo"); + const frame = RelativeVolumeFrame.fromFields(); // Act / Assert PropertyTests.propertyRoundTrip( @@ -606,7 +633,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @params(4, "v4") public render_noChannelData(version: number) { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foobarbaz"); + const frame = RelativeVolumeFrame.fromFields("foobarbaz"); // Act const output = frame.render(version); @@ -623,7 +650,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @params(4, "v4") public render_oneChannelData(version: number) { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foobarbaz"); + const frame = RelativeVolumeFrame.fromFields("foobarbaz"); frame.setPeakBits(ChannelType.Subwoofer, 32); frame.setPeakVolume(ChannelType.Subwoofer, BigInt(12345)); frame.setVolumeAdjustment(ChannelType.Subwoofer, -1.23); @@ -650,7 +677,7 @@ const assertFrame = (frame: RelativeVolumeFrame, c: ChannelData[], i: string) => @params(4, "v4") public render_twoChannelData(version: number) { // Arrange - const frame = RelativeVolumeFrame.fromIdentification("foobarbaz"); + const frame = RelativeVolumeFrame.fromFields("foobarbaz"); frame.setPeakBits(ChannelType.Subwoofer, 32); frame.setPeakVolume(ChannelType.Subwoofer, BigInt(12345)); frame.setVolumeAdjustment(ChannelType.Subwoofer, -1.23); diff --git a/test-unit/id3v2/synchronizedLyricsFrameTests.ts b/test-unit/id3v2/synchronizedLyricsFrameTests.ts index e719003a..ddcf582b 100644 --- a/test-unit/id3v2/synchronizedLyricsFrameTests.ts +++ b/test-unit/id3v2/synchronizedLyricsFrameTests.ts @@ -70,35 +70,6 @@ const assertFrame = ( return SynchronizedLyricsFrame.fromFieldBytes; } - @test - public fromInfo_withoutEncoding() { - // Arrange - const description = "fux"; - const language = "bux"; - const textType = SynchronizedTextType.Lyrics; - - // Act - const frame = SynchronizedLyricsFrame.fromInfo(description, language, textType); - - // Assert - assertFrame(frame, description, TimestampFormat.Unknown, language, [], Id3v2Settings.defaultEncoding, textType); - } - - @test - public fromInfo_withEncoding() { - // Arrange - const description = "fux"; - const encoding = StringType.UTF16BE; - const language = "bux"; - const textType = SynchronizedTextType.Lyrics; - - // Act - const frame = SynchronizedLyricsFrame.fromInfo(description, language, textType, encoding); - - // Assert - assertFrame(frame, description, TimestampFormat.Unknown, language, [], encoding, textType); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -304,13 +275,152 @@ const assertFrame = ( SynchronizedTextType.Events ); } + + @test + public fromFields_noParams() { + // Act + const frame = SynchronizedLyricsFrame.fromFields(); + + // Assert + assertFrame( + frame, + "", + TimestampFormat.Unknown, + "XXX", + [], + Id3v2Settings.defaultEncoding, + SynchronizedTextType.Other + ); + } + + @test + public fromFields_withDescription() { + // Act + const frame = SynchronizedLyricsFrame.fromFields("foo"); + + // Assert + assertFrame( + frame, + "foo", + TimestampFormat.Unknown, + "XXX", + [], + Id3v2Settings.defaultEncoding, + SynchronizedTextType.Other + ); + } + + @test + public fromFields_withDescriptionLyrics() { + // Arrange + const lyric = new SynchronizedText(1234, "bar"); + + // Act + const frame = SynchronizedLyricsFrame.fromFields("foo", [lyric]); + + // Assert + assertFrame( + frame, + "foo", + TimestampFormat.Unknown, + "XXX", + [lyric], + Id3v2Settings.defaultEncoding, + SynchronizedTextType.Other + ); + } + + @test + public fromFields_withDescriptionLyricsLanguage() { + // Arrange + const lyric = new SynchronizedText(1234, "bar"); + + // Act + const frame = SynchronizedLyricsFrame.fromFields("foo", [lyric], "eng"); + + // Assert + assertFrame( + frame, + "foo", + TimestampFormat.Unknown, + "eng", + [lyric], + Id3v2Settings.defaultEncoding, + SynchronizedTextType.Other + ); + } + + @test + public fromFields_withDescriptionLyricsLanguageType() { + // Arrange + const lyric = new SynchronizedText(1234, "bar"); + + // Act + const frame = SynchronizedLyricsFrame.fromFields("foo", [lyric], "eng", SynchronizedTextType.Chord); + + // Assert + assertFrame( + frame, + "foo", + TimestampFormat.Unknown, + "eng", + [lyric], + Id3v2Settings.defaultEncoding, + SynchronizedTextType.Chord + ); + } + + @test + public fromFields_withDescriptionLyricsLanguageTypeEncoding() { + // Arrange + const lyric = new SynchronizedText(1234, "bar"); + + // Act + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric], + "eng", + SynchronizedTextType.Chord, + StringType.Hex + ); + + // Assert + assertFrame(frame, "foo", TimestampFormat.Unknown, "eng", [lyric], StringType.Hex, SynchronizedTextType.Chord); + } + + @test + public fromFields_withDescriptionLyricsLanguageTypeEncodingType() { + // Arrange + const lyric = new SynchronizedText(1234, "bar"); + + // Act + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric], + "eng", + SynchronizedTextType.Chord, + StringType.Hex, + TimestampFormat.AbsoluteMpegFrames + ); + + // Assert + assertFrame( + frame, + "foo", + TimestampFormat.AbsoluteMpegFrames, + "eng", + [lyric], + StringType.Hex, + SynchronizedTextType.Chord + ); + } } @suite class Id3v2_SynchronizedLyricsFrame_PropertyTests { @test public description() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo"); const set = (v: string) => { frame.description = v; }; const get = () => frame.description; @@ -322,7 +432,7 @@ const assertFrame = ( @test public format() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo"); // Act / Assert PropertyTests.propertyRoundTrip( @@ -335,7 +445,7 @@ const assertFrame = ( @test public language() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo"); const set = (v: string) => { frame.language = v; }; const get = () => frame.language; @@ -348,7 +458,7 @@ const assertFrame = ( @test public text() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo",); const set = (v: SynchronizedText[]) => { frame.text = v; }; const get = () => frame.text; const value = [new SynchronizedText(123, "foo")]; @@ -362,7 +472,7 @@ const assertFrame = ( @test public textEncoding() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo"); // Act / Assert PropertyTests.propertyRoundTrip( @@ -375,7 +485,7 @@ const assertFrame = ( @test public textType() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord); + const frame = SynchronizedLyricsFrame.fromFields("foo"); // Act / Assert PropertyTests.propertyRoundTrip( @@ -390,13 +500,24 @@ const assertFrame = ( @test public clone() { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("fux", "bux", SynchronizedTextType.Chord); + const lyric1 = new SynchronizedText(123, "bar"); + const lyric2 = new SynchronizedText(234, "baz"); + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric1, lyric2], + "eng", + SynchronizedTextType.Chord, + StringType.UTF16BE, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.clone(); // Assert - assertFrame(output, + // @TODO: Verify that objects that are cloned do a deep clone + assertFrame( + output, frame.description, frame.format, frame.language, @@ -428,8 +549,8 @@ const assertFrame = ( @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -443,8 +564,8 @@ const assertFrame = ( @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Other); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = SynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -458,9 +579,9 @@ const assertFrame = ( @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Other); - const frame3 = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Other); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = SynchronizedLyricsFrame.fromFields(); + const frame3 = SynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -475,8 +596,8 @@ const assertFrame = ( @test public filterFrames_allMatches() { // Arrange - const frame1 = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Other); - const frame2 = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Other); + const frame1 = SynchronizedLyricsFrame.fromFields(); + const frame2 = SynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -492,8 +613,14 @@ const assertFrame = ( @params(4, "v4") public render_noLyrics(version: number) { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord, StringType.Latin1); - frame.format = TimestampFormat.AbsoluteMpegFrames; + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [], + "bar", + SynchronizedTextType.Chord, + StringType.Latin1, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.render(version); @@ -519,9 +646,14 @@ const assertFrame = ( @params(4, "v4") public render_falsyLyrics(version: number) { // Arrange - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord, StringType.Latin1); - frame.format = TimestampFormat.AbsoluteMpegFrames; - frame.text = [undefined, null]; + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [undefined, null], + "bar", + SynchronizedTextType.Chord, + StringType.Latin1, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.render(version); @@ -548,10 +680,14 @@ const assertFrame = ( public render_oneLyric(version: number) { // Arrange const lyric1 = new SynchronizedText(123, "fux"); - - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord, StringType.Latin1); - frame.format = TimestampFormat.AbsoluteMpegFrames; - frame.text = [lyric1]; + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric1], + "bar", + SynchronizedTextType.Chord, + StringType.Latin1, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.render(version); @@ -580,10 +716,14 @@ const assertFrame = ( // Arrange const lyric2 = new SynchronizedText(234, "bux"); const lyric1 = new SynchronizedText(123, "fux"); - - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord, StringType.Latin1); - frame.format = TimestampFormat.AbsoluteMpegFrames; - frame.text = [lyric2, lyric1]; // Note - not in chronological order + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric2, lyric1], // Note - not in chronological order + "bar", + SynchronizedTextType.Chord, + StringType.Latin1, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.render(version); @@ -612,10 +752,14 @@ const assertFrame = ( // Arrange const lyric2 = new SynchronizedText(234, "bux"); const lyric1 = new SynchronizedText(123, "fux"); - - const frame = SynchronizedLyricsFrame.fromInfo("foo", "bar", SynchronizedTextType.Chord, encoding); - frame.format = TimestampFormat.AbsoluteMpegFrames; - frame.text = [lyric2, lyric1]; // Note - not in chronological order + const frame = SynchronizedLyricsFrame.fromFields( + "foo", + [lyric2, lyric1], // Note - not in chronological order + "bar", + SynchronizedTextType.Chord, + encoding, + TimestampFormat.AbsoluteMpegFrames + ); // Act const output = frame.render(4); diff --git a/test-unit/id3v2/termsOfUseFrameTests.ts b/test-unit/id3v2/termsOfUseFrameTests.ts index b27493bb..6f989254 100644 --- a/test-unit/id3v2/termsOfUseFrameTests.ts +++ b/test-unit/id3v2/termsOfUseFrameTests.ts @@ -27,24 +27,6 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex return TermsOfUseFrame.fromFieldBytes; } - @test - public fromFields_withoutTextEncoding() { - // Act - const output = TermsOfUseFrame.fromFields("fux"); - - // Assert - assertFrame(output, "fux", "", Id3v2Settings.defaultEncoding); - } - - @test - public fromFields_withTextEncoding() { - // Act - const output = TermsOfUseFrame.fromFields("fux", StringType.UTF16BE); - - // Assert - assertFrame(output, "fux", "", StringType.UTF16BE); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -111,6 +93,42 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex // Assert assertFrame(frame, "eng", "foobarbaz", encoding); } + + @test + public fromFields_noParams() { + // Act + const output = TermsOfUseFrame.fromFields(); + + // Assert + assertFrame(output, "XXX", "", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withText() { + // Act + const output = TermsOfUseFrame.fromFields("foo"); + + // Assert + assertFrame(output, "XXX", "foo", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withTextLanguage() { + // Act + const output = TermsOfUseFrame.fromFields("foo", "bar"); + + // Assert + assertFrame(output, "bar", "foo", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withTextLanguageEncoding() { + // Act + const output = TermsOfUseFrame.fromFields("foo", "bar", StringType.UTF16BE); + + // Assert + assertFrame(output, "bar", "foo", StringType.UTF16BE); + } } @suite class Id3v2_TermsOfUseFrame_PropertyTests { @@ -121,7 +139,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex const get = () => frame.language; // Act/Assert - const frame = TermsOfUseFrame.fromFields("eng"); + const frame = TermsOfUseFrame.fromFields(); PropertyTests.propertyNormalized(set, get, "fu", "XXX"); PropertyTests.propertyNormalized(set, get, "fuxx", "fux"); PropertyTests.propertyNormalized(set, get, undefined, "XXX"); @@ -135,7 +153,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex const set = (v: string) => { frame.text = v; }; const get = () => frame.text; - const frame = TermsOfUseFrame.fromFields("eng"); + const frame = TermsOfUseFrame.fromFields(); PropertyTests.propertyRoundTrip(set, get, "fux"); PropertyTests.propertyNormalized(set, get, undefined, ""); PropertyTests.propertyNormalized(set, get, null, ""); @@ -143,7 +161,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public textEncoding() { - const frame = TermsOfUseFrame.fromFields("eng", StringType.Latin1); + const frame = TermsOfUseFrame.fromFields(); PropertyTests.propertyRoundTrip( (v) => { frame.textEncoding = v; }, () => frame.textEncoding, @@ -175,8 +193,8 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -190,8 +208,8 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = TermsOfUseFrame.fromFields("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = TermsOfUseFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -205,9 +223,9 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = TermsOfUseFrame.fromFields("foo"); - const frame3 = TermsOfUseFrame.fromFields("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = TermsOfUseFrame.fromFields(); + const frame3 = TermsOfUseFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -222,8 +240,8 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public filterFrames_allMatches() { // Arrange - const frame1 = TermsOfUseFrame.fromFields("foo"); - const frame2 = TermsOfUseFrame.fromFields("bar"); + const frame1 = TermsOfUseFrame.fromFields(); + const frame2 = TermsOfUseFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -237,8 +255,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public clone() { // Arrange - const frame = TermsOfUseFrame.fromFields("eng", StringType.Latin1); - frame.text = "foobarbux"; + const frame = TermsOfUseFrame.fromFields("foobarbaz", "eng", StringType.Latin1); // Act const output = frame.clone(); @@ -250,7 +267,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @test public render_v2() { // Arrange - const frame = TermsOfUseFrame.fromFields("foo", StringType.Latin1); + const frame = TermsOfUseFrame.fromFields("foo", "bar", StringType.Latin1); // Act / Assert assert.throws(() => frame.render(2)); @@ -261,8 +278,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @params("", "empty") public render_noText(text: string) { // Arrange - const frame = TermsOfUseFrame.fromFields("foo", StringType.Latin1); - frame.text = text; + const frame = TermsOfUseFrame.fromFields(text, "foo", StringType.Latin1); // Act const result = frame.render(4); @@ -281,8 +297,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @params(4, "v4") public render_withText(version: number) { // Arrange - const frame = TermsOfUseFrame.fromFields("foo", StringType.Latin1); - frame.text = "bar"; + const frame = TermsOfUseFrame.fromFields("bar", "foo", StringType.Latin1); // Act const result = frame.render(version); @@ -302,8 +317,7 @@ const assertFrame = (frame: TermsOfUseFrame, language: string, text: string, tex @params(StringType.UTF16BE, "multi_byte") public render_encodingTest(encoding: StringType) { // Arrange - const frame = TermsOfUseFrame.fromFields("foo", encoding); - frame.text = "bar"; + const frame = TermsOfUseFrame.fromFields("bar", "foo", encoding); // Act const result = frame.render(4); diff --git a/test-unit/id3v2/textInformationFrameTests.ts b/test-unit/id3v2/textInformationFrameTests.ts index 915f171e..9da722c6 100644 --- a/test-unit/id3v2/textInformationFrameTests.ts +++ b/test-unit/id3v2/textInformationFrameTests.ts @@ -22,42 +22,11 @@ const assertFrame = (frame: TextInformationFrame, frameId: FrameIdentifier, text assert.strictEqual(frame.textEncoding, encoding); } -const getTestFrame = (): TextInformationFrame => { - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP, StringType.Latin1); - frame.text = ["foo", "bar"]; - - return frame; -} - @suite class Id3v2_TextInformationFrame_ConstructorTests extends FrameConstructorTests { public get fromFieldBytes(): (h: Id3v2FrameHeader, d: ByteVector, v: number) => Frame { return TextInformationFrame.fromFieldBytes; } - @test - public fromIdentifier_falsyIdentifier() { - // Act/Assert - Testers.testTruthy((v: FrameIdentifier) => { TextInformationFrame.fromIdentifier(v); }); - } - - @test - public fromIdentifier_noEncoding_returnsFrameWithDefaultEncoding() { - // Act - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - - // Assert - assertFrame(frame, FrameIdentifiers.TCOP, [], Id3v2Settings.defaultEncoding); - } - - @test - public fromIdentifier_withEncoding_returnsFrameWithProvidedEncoding() { - // Act - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP, StringType.Latin1); - - // Assert - assertFrame(frame, FrameIdentifiers.TCOP, [], StringType.Latin1); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -174,6 +143,39 @@ const getTestFrame = (): TextInformationFrame => { // Assert assertFrame(frame,FrameIdentifiers.TCOP, ["fux", "bux"], encoding); } + + @test + public fromFields_falsyIdentifier() { + // Act/Assert + Testers.testTruthy((v: FrameIdentifier) => { TextInformationFrame.fromFields(v); }); + } + + @test + public fromFields_withIdentifier() { + // Act + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); + + // Assert + assertFrame(frame, FrameIdentifiers.TCOP, [], Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withIdentifierText() { + // Act + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["foo", "bar"]); + + // Assert + assertFrame(frame, FrameIdentifiers.TCOP, ["foo", "bar"], Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withIdentifierTextEncoding() { + // Act + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["foo", "bar"], StringType.UTF16BE); + + // Assert + assertFrame(frame, FrameIdentifiers.TCOP, ["foo", "bar"], StringType.UTF16BE); + } } @suite class Id3v2_TextInformationFrame_PropertyTests { @@ -183,7 +185,7 @@ const getTestFrame = (): TextInformationFrame => { @params(["bar"], "truthy") public text_values(value: string[]) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); // Act frame.text = value; @@ -195,7 +197,7 @@ const getTestFrame = (): TextInformationFrame => { @test public encoding() { // Arrange - const frame = getTestFrame(); + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["foo"]); const get = () => frame.textEncoding; const set = (v: StringType) => { frame.textEncoding = v; }; @@ -208,7 +210,7 @@ const getTestFrame = (): TextInformationFrame => { @test public clone_returnsCopy() { // Arrange - const frame = getTestFrame(); + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["foo"], StringType.UTF16LE); // Act const output = frame.clone(); @@ -239,8 +241,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_noIdentifier_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -254,8 +256,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_noIdentifier_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -269,9 +271,9 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_noIdentifier_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame3 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); + const frame3 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2, frame3]; @@ -286,8 +288,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_noIdentifier_allMatches() { // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -314,8 +316,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_withIdentifier_noMatch() { // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); const frames = [frame1, frame2]; // Act @@ -329,8 +331,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_withIdentifier_singleMatch() { // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -344,9 +346,9 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_withIdentifier_multipleMatches() { // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame3 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); + const frame3 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2, frame3]; @@ -361,8 +363,8 @@ const getTestFrame = (): TextInformationFrame => { @test public filterFrames_withIdentifier_allMatches() { // Arrange - const frame1 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame2 = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); + const frame2 = TextInformationFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -378,8 +380,7 @@ const getTestFrame = (): TextInformationFrame => { @params(4, "v4") public render_empty_returnEmptyVector(version: number) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TALB); - frame.text = []; + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TALB, []); // Act const result = frame.render(version); @@ -394,8 +395,7 @@ const getTestFrame = (): TextInformationFrame => { @params(4, "v4") public render_falsyValues_returnEmptyVector(version: number) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TALB); - frame.text = [undefined, null, ""]; + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TALB, [undefined, null, ""]); // Act const result = frame.render(version); @@ -409,8 +409,7 @@ const getTestFrame = (): TextInformationFrame => { @params(StringType.UTF16BE, "multi_byte") public render_v4EncodingTest(encoding: StringType) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TALB, encoding); - frame.text = ["foo", "bar", "baz"] + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TALB, ["foo", "bar", "baz"], encoding); // Act const result = frame.render(4); @@ -437,8 +436,7 @@ const getTestFrame = (): TextInformationFrame => { @params([3, StringType.UTF16BE], "v3_multi_byte") public render_v3WithSplit([version, encoding]: [number, StringType]) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP, encoding); - frame.text = ["fux", "bux"] + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["fux", "bux"], encoding); // Act const output = frame.render(version); @@ -461,8 +459,7 @@ const getTestFrame = (): TextInformationFrame => { @params([3, StringType.UTF16BE], "v3_multi_byte") public render_v3WithoutSplit([version, encoding]: [number, StringType]) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP, encoding); - frame.text = ["fux"]; + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, ["fux"], encoding); // Act const output = frame.render(version); @@ -484,8 +481,7 @@ const getTestFrame = (): TextInformationFrame => { @params([["foo", "bar"], "foo; bar"], "multiple") public toString_returnsText([text, expected]: [string[], string]) { // Arrange - const frame = TextInformationFrame.fromIdentifier(FrameIdentifiers.TCOP); - frame.text = text; + const frame = TextInformationFrame.fromFields(FrameIdentifiers.TCOP, text); // Act const result = frame.toString(); diff --git a/test-unit/id3v2/uniqueFileIdentifierFrameTests.ts b/test-unit/id3v2/uniqueFileIdentifierFrameTests.ts index 3d7ec9b8..5696b4b2 100644 --- a/test-unit/id3v2/uniqueFileIdentifierFrameTests.ts +++ b/test-unit/id3v2/uniqueFileIdentifierFrameTests.ts @@ -21,11 +21,7 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) assert.strictEqual(frame.frameId, FrameIdentifiers.UFID); assert.strictEqual(frame.owner, o); - if (i !== undefined) { - Testers.bvEqual(frame.identifier, i); - } else { - assert.isUndefined(frame.identifier); - } + Testers.bvEqual(frame.identifier, i); } @suite class Id3v2_UniqueFileIdentifierFrame_ConstructorTests extends FrameConstructorTests { @@ -33,37 +29,6 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) return UniqueFileIdentifierFrame.fromFieldBytes; } - @test - public fromData_invalidOwner_throws() { - // Arrange - const identifier = ByteVector.empty(); - - // Act/Assert - Testers.testTruthy((v: string) => { UniqueFileIdentifierFrame.fromData(v, identifier); }); - } - - @test - public fromData_invalidIdentifier_throws() { - // Arrange - const owner = "fuxqux"; - - // Act/Assert - assert.throws(() => { UniqueFileIdentifierFrame.fromData(owner, ByteVector.fromSize(65)); }); - } - - @test - public fromData_validPrams() { - // Arrange - const owner = "fuxqux"; - const identifier = ByteVector.fromSize(32, 0x8); - - // Act - const frame = UniqueFileIdentifierFrame.fromData(owner, identifier); - - // Assert - assertFrame(frame, owner, identifier); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -127,13 +92,49 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) // Assert assertFrame(frame, testOwner, testIdentifier); } + + @test + public fromFields_noParams() { + // Act + const frame = UniqueFileIdentifierFrame.fromFields(); + + // Assert + assertFrame(frame, "", ByteVector.empty()); + } + + @test + public fromFields_withOwner() { + // Act + const frame = UniqueFileIdentifierFrame.fromFields("foo"); + + // Assert + assertFrame(frame, "foo", ByteVector.empty()); + } + + @test + public fromFields_invalidIdentifier_throws() { + // Act / Assert + assert.throws(() => { UniqueFileIdentifierFrame.fromFields("foo", ByteVector.fromSize(65)); }); + } + + @test + public fromFields_withOwnerIdentifier() { + // Arrange + const identifier = ByteVector.fromSize(32, 0x8); + + // Act + const frame = UniqueFileIdentifierFrame.fromFields("foo", identifier); + + // Assert + assertFrame(frame, "foo", identifier); + } } @suite class Id3v2_UniqueFileIdentifierFrame_PropertyTests { @test public setIdentifier_tooLong_throws() { // Arrange - const frame = UniqueFileIdentifierFrame.fromData("fuxqux", ByteVector.fromSize(1)); + const frame = UniqueFileIdentifierFrame.fromFields("fuxqux", ByteVector.fromSize(1)); // Act/Assert PropertyTests.propertyThrows((v) => { frame.identifier = v; }, ByteVector.fromSize(65)); @@ -142,7 +143,7 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public setIdentifier_valid() { // Arrange - const frame = UniqueFileIdentifierFrame.fromData("fuxqux", ByteVector.fromSize(1)); + const frame = UniqueFileIdentifierFrame.fromFields("fuxqux", ByteVector.fromSize(1)); const identifier = ByteVector.fromString("quxx", StringType.UTF8); // Act / Assert @@ -154,7 +155,7 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public clone_noIdentifier() { // Arrange - const frame = UniqueFileIdentifierFrame.fromData("fux", undefined); + const frame = UniqueFileIdentifierFrame.fromFields("fux", undefined); // Act const clone = frame.clone(); @@ -166,7 +167,7 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public clone_withIdentifier() { // Arrange - const frame = UniqueFileIdentifierFrame.fromData("fux", ByteVector.fromString("qux", StringType.UTF8)); + const frame = UniqueFileIdentifierFrame.fromFields("fux", ByteVector.fromString("qux", StringType.UTF8)); // Act const clone = frame.clone(); @@ -197,8 +198,8 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -212,8 +213,8 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.empty()); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UniqueFileIdentifierFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -227,9 +228,9 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.empty()); - const frame3 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.empty()); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UniqueFileIdentifierFrame.fromFields(); + const frame3 = UniqueFileIdentifierFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -244,8 +245,8 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @test public filterFrames_allMatches() { // Arrange - const frame1 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.empty()); - const frame2 = UniqueFileIdentifierFrame.fromData("foo", ByteVector.empty()); + const frame1 = UniqueFileIdentifierFrame.fromFields(); + const frame2 = UniqueFileIdentifierFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -261,7 +262,7 @@ const assertFrame = (frame: UniqueFileIdentifierFrame, o: string, i: ByteVector) @params(4, "v4") public render_returnsByteVector(version: number) { // Arrange - const frame = UniqueFileIdentifierFrame.fromData(testOwner, testIdentifier); + const frame = UniqueFileIdentifierFrame.fromFields(testOwner, testIdentifier); // Act const result = frame.render(version); diff --git a/test-unit/id3v2/unknownFrameTests.ts b/test-unit/id3v2/unknownFrameTests.ts index 5f98b4fb..60596f75 100644 --- a/test-unit/id3v2/unknownFrameTests.ts +++ b/test-unit/id3v2/unknownFrameTests.ts @@ -27,51 +27,50 @@ const assertFrame = (frame: UnknownFrame, fi: FrameIdentifier, d: ByteVector) => return UnknownFrame.fromFieldBytes; } - @test - public fromData_falsyType_throws() { - // Act/Assert - Testers.testTruthy((v: FrameIdentifier) => { UnknownFrame.fromData(v, undefined); }); - } - - @params(undefined, "undefined") - @params(null, "null") - public fromData_falsyData_frameHasNoData(value: ByteVector) { + @params(2, "v2") + @params(3, "v3") + @params(4, "v4") + public fromFieldBytes_validParams_returnsFrame(version: number) { // Arrange - const frameType = FrameIdentifiers.WXXX; + const fieldBytes = ByteVector.fromString("foo bar baz", StringType.UTF8); + const header = new Id3v2FrameHeader(FrameIdentifiers.WXXX, Id3v2FrameFlags.None, fieldBytes.length); // Act - const frame = UnknownFrame.fromData(frameType, value); + const frame = UnknownFrame.fromFieldBytes(header, fieldBytes, version); // Assert - assertFrame(frame, FrameIdentifiers.WXXX, undefined); + assertFrame(frame, FrameIdentifiers.WXXX, ByteVector.fromString("foo bar baz", StringType.UTF8)); } @test - public fromData_withData_frameHasData() { + public fromFields_falsyType_throws() { + // Act/Assert + Testers.testTruthy((v: FrameIdentifier) => { UnknownFrame.fromFields(v, undefined); }); + } + + @test + public fromFields_withIdentifier() { // Arrange - const frameType = FrameIdentifiers.WXXX; - const data = ByteVector.fromString("fux qux quxx", StringType.UTF8); + const frameType = FrameIdentifiers.RVRB; // Act - const frame = UnknownFrame.fromData(frameType, data); + const frame = UnknownFrame.fromFields(frameType); // Assert - assertFrame(frame, FrameIdentifiers.WXXX, data); + assertFrame(frame, FrameIdentifiers.RVRB, ByteVector.empty()); } - @params(2, "v2") - @params(3, "v3") - @params(4, "v4") - public fromFieldBytes_validParams_returnsFrame(version: number) { + @test + public fromFields_withIdentifierData() { // Arrange - const fieldBytes = ByteVector.fromString("foo bar baz", StringType.UTF8); - const header = new Id3v2FrameHeader(FrameIdentifiers.WXXX, Id3v2FrameFlags.None, fieldBytes.length); + const frameType = FrameIdentifiers.RVRB; + const data = ByteVector.fromString("fux qux quxx", StringType.UTF8); // Act - const frame = UnknownFrame.fromFieldBytes(header, fieldBytes, version); + const frame = UnknownFrame.fromFields(frameType, data); // Assert - assertFrame(frame, FrameIdentifiers.WXXX, ByteVector.fromString("foo bar baz", StringType.UTF8)); + assertFrame(frame, FrameIdentifiers.RVRB, data); } } @@ -114,8 +113,8 @@ const assertFrame = (frame: UnknownFrame, fi: FrameIdentifier, d: ByteVector) => @test public filterFrames_noMatch() { // Arrange - const frame1 = CommentsFrame.fromDescription("foo"); - const frame2 = CommentsFrame.fromDescription("foo"); + const frame1 = CommentsFrame.fromFields(); + const frame2 = CommentsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -129,8 +128,8 @@ const assertFrame = (frame: UnknownFrame, fi: FrameIdentifier, d: ByteVector) => @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = CommentsFrame.fromDescription("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = CommentsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -144,9 +143,9 @@ const assertFrame = (frame: UnknownFrame, fi: FrameIdentifier, d: ByteVector) => @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame3 = CommentsFrame.fromDescription("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame3 = CommentsFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -161,8 +160,8 @@ const assertFrame = (frame: UnknownFrame, fi: FrameIdentifier, d: ByteVector) => @test public filterFrames_allMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act diff --git a/test-unit/id3v2/unsynchronizedLyricsFrameTests.ts b/test-unit/id3v2/unsynchronizedLyricsFrameTests.ts index 761ae69d..aaeddd76 100644 --- a/test-unit/id3v2/unsynchronizedLyricsFrameTests.ts +++ b/test-unit/id3v2/unsynchronizedLyricsFrameTests.ts @@ -3,6 +3,7 @@ import {assert} from "chai"; import Frame from "../../src/id3v2/frames/frame"; import FrameConstructorTests from "./frameConstructorTests"; +import Id3v2Settings from "../../src/id3v2/id3v2Settings"; import PropertyTests from "../utilities/propertyTests"; import UnknownFrame from "../../src/id3v2/frames/unknownFrame"; import UnsynchronizedLyricsFrame from "../../src/id3v2/frames/unsynchronizedLyricsFrame"; @@ -11,18 +12,6 @@ import {Id3v2FrameFlags, Id3v2FrameHeader} from "../../src/id3v2/frames/frameHea import {FrameIdentifiers} from "../../src/id3v2/frameIdentifiers"; import {Testers} from "../utilities/testers"; -const getTestUnsynchronizedLyricsFrame = (): UnsynchronizedLyricsFrame => { - const fieldBytes = ByteVector.concatenate( - StringType.Latin1, // Encoding - ByteVector.fromString("eng", StringType.Latin1), // Language - ByteVector.fromString("foo", StringType.Latin1), // Description - ByteVector.getTextDelimiter(StringType.Latin1), // Delimiter - ByteVector.fromString("bar", StringType.Latin1) // Content - ); - const header = new Id3v2FrameHeader(FrameIdentifiers.USLT); - return UnsynchronizedLyricsFrame.fromFieldBytes(header, fieldBytes, 4); -}; - const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: string, te: StringType) => { assert.isOk(frame); assert.instanceOf(frame, UnsynchronizedLyricsFrame); @@ -39,21 +28,6 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: return UnsynchronizedLyricsFrame.fromFieldBytes; } - @test - public fromData() { - // @TODO: Add test cases for different values - especially undefined language. - // Arrange - const encoding = StringType.Latin1; - const language = "eng"; - const description = "foo"; - - // Act - const frame = UnsynchronizedLyricsFrame.fromData(description, language, encoding); - - // Assert - assertFrame(frame, description, language, "", encoding); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -163,13 +137,58 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: // Assert assertFrame(frame, "foo", "eng", "bar", encoding); } + + @test + public fromFields_noParams() { + // Act + const frame = UnsynchronizedLyricsFrame.fromFields(); + + // Assert + assertFrame(frame, "", "XXX", "", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withDescription() { + // Act + const frame = UnsynchronizedLyricsFrame.fromFields("foo"); + + // Assert + assertFrame(frame, "foo", "XXX", "", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withDescriptionText() { + // Act + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar"); + + // Assert + assertFrame(frame, "foo", "XXX", "bar", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withDescriptionTextLanguage() { + // Act + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar", "eng"); + + // Assert + assertFrame(frame, "foo", "eng", "bar", Id3v2Settings.defaultEncoding); + } + + @test + public fromFields_withDescriptionTextLanguageEncoding() { + // Act + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar", "eng", StringType.Hex); + + // Assert + assertFrame(frame, "foo", "eng", "bar", StringType.Hex); + } } @suite class Id3v2_UnsynchronizedLyricsFrame_PropertyTests { @test public description() { // Arrange - const frame = getTestUnsynchronizedLyricsFrame(); + const frame = UnsynchronizedLyricsFrame.fromFields(); const set = (v: string) => { frame.description = v; }; const get = () => frame.description; @@ -182,7 +201,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public language() { // Arrange - const frame = getTestUnsynchronizedLyricsFrame(); + const frame = UnsynchronizedLyricsFrame.fromFields(); const set = (v: string) => { frame.language = v; }; const get = () => frame.language; @@ -197,7 +216,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public text() { // Arrange - const frame = getTestUnsynchronizedLyricsFrame(); + const frame = UnsynchronizedLyricsFrame.fromFields(); const set = (v: string) => { frame.text = v; }; const get = () => frame.text; @@ -210,7 +229,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public textEncoding() { // Arrange - const frame = getTestUnsynchronizedLyricsFrame(); + const frame = UnsynchronizedLyricsFrame.fromFields(); // Act / Assert PropertyTests.propertyRoundTrip( @@ -225,7 +244,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public clone() { // Arrange - const frame = getTestUnsynchronizedLyricsFrame(); + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar", "baz", StringType.Hex); // Act const result = frame.clone(); @@ -256,8 +275,8 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -271,8 +290,8 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnsynchronizedLyricsFrame.fromData("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnsynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -286,9 +305,9 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnsynchronizedLyricsFrame.fromData("foo"); - const frame3 = UnsynchronizedLyricsFrame.fromData("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnsynchronizedLyricsFrame.fromFields(); + const frame3 = UnsynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2, frame3]; @@ -303,8 +322,8 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public filterFrames_allMatches() { // Arrange - const frame1 = UnsynchronizedLyricsFrame.fromData("foo"); - const frame2 = UnsynchronizedLyricsFrame.fromData("bar"); + const frame1 = UnsynchronizedLyricsFrame.fromFields(); + const frame2 = UnsynchronizedLyricsFrame.fromFields(); const frames = [frame1, frame2]; // Act @@ -323,8 +342,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @params([4, StringType.UTF16BE], "v4_multibyte") public render([version, encoding]: [number, StringType]) { // Arrange - const frame = UnsynchronizedLyricsFrame.fromData("foo", "eng", encoding); - frame.text = "bar"; + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar", "eng", encoding); // Act const output = frame.render(version); @@ -349,8 +367,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @params([4, StringType.UTF8], "v4") public render_utf8([version, outputEncoding]: [number, StringType]) { // Arrange - const frame = UnsynchronizedLyricsFrame.fromData("foo", "eng", StringType.UTF8); - frame.text = "bar"; + const frame = UnsynchronizedLyricsFrame.fromFields("foo", "bar", "eng", StringType.UTF8); // Act const output = frame.render(version); @@ -373,7 +390,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public render_descriptionOnly() { // Arrange - const frame = UnsynchronizedLyricsFrame.fromData("foo", "eng", StringType.Latin1); + const frame = UnsynchronizedLyricsFrame.fromFields("foo", undefined, "eng", StringType.Latin1); // Act const result = frame.render(4); @@ -395,8 +412,7 @@ const assertFrame = (frame: UnsynchronizedLyricsFrame, d: string, l: string, t: @test public render_lyricsOnly() { // Arrange - const frame = UnsynchronizedLyricsFrame.fromData("", "eng", StringType.Latin1); - frame.text = "foo"; + const frame = UnsynchronizedLyricsFrame.fromFields("", "foo", "eng", StringType.Latin1); // Act const result = frame.render(4); diff --git a/test-unit/id3v2/urlLinkFrameTests.ts b/test-unit/id3v2/urlLinkFrameTests.ts index 0d49f1b8..9a18a100 100644 --- a/test-unit/id3v2/urlLinkFrameTests.ts +++ b/test-unit/id3v2/urlLinkFrameTests.ts @@ -14,7 +14,7 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str assert.ok(frame); assert.instanceOf(frame, UrlLinkFrame); assert.strictEqual(frame.frameId, identifier); - assert.strictEqual(frame.text, text); + assert.strictEqual(frame.url, text); } @suite class Id3v2_UrlLinkFrame_ConstructorTests extends FrameConstructorTests { @@ -22,21 +22,6 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str return UrlLinkFrame.fromFieldBytes; } - @test - public fromIdentifier_falsyIdentity() { - // Act/Assert - Testers.testTruthy((v: FrameIdentifier) => { UrlLinkFrame.fromIdentifier(v); }); - } - - @test - public fromIdentifier_validIdentity() { - // Act - const output = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - - // Assert - assertFrame(output, FrameIdentifiers.WCOM, undefined); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -89,6 +74,30 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str // Assert assertFrame(output, FrameIdentifiers.WCOM, "foo"); } + + @test + public fromFields_falsyIdentifier() { + // Act/Assert + Testers.testTruthy((v: FrameIdentifier) => { UrlLinkFrame.fromFields(v); }); + } + + @test + public fromFields_withIdentifier() { + // Act + const output = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM); + + // Assert + assertFrame(output, FrameIdentifiers.WCOM, ""); + } + + @test + public fromFields_withIdentifierUrl() { + // Act + const output = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, "foo"); + + // Assert + assertFrame(output, FrameIdentifiers.WCOM, "foo"); + } } @suite class Id3v2_UrlLinkFrame_PropertyTests { @@ -98,13 +107,13 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @params("bar", "truthy") public setText_falsyValues(value: string) { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM); // Act - frame.text = value; + frame.url = value; // Assert - assert.strictEqual(frame.text, value); + assert.strictEqual(frame.url, value); } } @@ -112,14 +121,13 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public clone_returnsCloneUsingRawData() { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = "foo"; + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, "foo"); // Act const result = frame.clone(); // Assert - assertFrame(result, frame.frameId, frame.text); + assertFrame(result, frame.frameId, frame.url); } @test @@ -144,8 +152,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_noIdentifier_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -159,8 +167,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_noIdentifier_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -174,9 +182,9 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_noIdentifier_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame3 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); + const frame3 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2, frame3]; @@ -191,8 +199,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_noIdentifier_allMatches() { // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -219,8 +227,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_withIdentifier_noMatch() { // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOM); + const frame1 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOM); const frames = [frame1, frame2]; // Act @@ -234,8 +242,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_withIdentifier_singleMatch() { // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -249,9 +257,9 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_withIdentifier_multipleMatches() { // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOM); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame3 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOM); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); + const frame3 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2, frame3]; @@ -266,8 +274,8 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public filterFrames_withIdentifier_allMatches() { // Arrange - const frame1 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); - const frame2 = UrlLinkFrame.fromIdentifier(FrameIdentifiers.TCOP); + const frame1 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); + const frame2 = UrlLinkFrame.fromFields(FrameIdentifiers.TCOP); const frames = [frame1, frame2]; // Act @@ -281,7 +289,7 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public render_withoutText() { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM); // Act const result = frame.render(4); @@ -294,8 +302,7 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @test public render_withText() { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = "foo"; + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, "foo"); // Act const result = frame.render(4); @@ -316,8 +323,7 @@ const assertFrame = (frame: UrlLinkFrame, identifier: FrameIdentifier, text: str @params("foo", "foo") public toString_returnsText(text: string) { // Arrange - const frame = UrlLinkFrame.fromIdentifier(FrameIdentifiers.WCOM); - frame.text = text; + const frame = UrlLinkFrame.fromFields(FrameIdentifiers.WCOM, text); // Act const result = frame.toString(); diff --git a/test-unit/id3v2/userTextInformationFrameTests.ts b/test-unit/id3v2/userTextInformationFrameTests.ts index fb601bfe..3065d217 100644 --- a/test-unit/id3v2/userTextInformationFrameTests.ts +++ b/test-unit/id3v2/userTextInformationFrameTests.ts @@ -27,41 +27,11 @@ const assertFrame = ( assert.strictEqual(frame.textEncoding, encoding); } -const getTestFrame = (): UserTextInformationFrame => { - const frame = UserTextInformationFrame.fromDescription("foo", StringType.Latin1); - frame.text = ["bar"]; - return frame; -} - @suite class Id3v2_UserInformationFrame_ConstructorTests extends FrameConstructorTests { public get fromFieldBytes(): (h: Id3v2FrameHeader, d: ByteVector, v: number) => Frame { return UserTextInformationFrame.fromFieldBytes; } - @params(undefined, "undefined") - @params(null, "null") - @params("", "empty_string") - @params("foo", "truthy") - public fromDescription_withoutEncoding(description: string) { - // Act - const frame = UserTextInformationFrame.fromDescription(description); - - // Assert - assertFrame(frame, description, [], Id3v2Settings.defaultEncoding); - } - - @params(undefined, "undefined") - @params(null, "null") - @params("", "empty_string") - @params("foo", "truthy") - public fromDescription_withEncoding(description: string) { - // Act - const frame = UserTextInformationFrame.fromDescription(description, StringType.UTF16BE); - - // Assert - assertFrame(frame, description, [], StringType.UTF16BE); - } - @params(2, "v2") @params(3, "v3") @params(4, "v4") @@ -167,64 +137,59 @@ const getTestFrame = (): UserTextInformationFrame => { // Assert assertFrame(output, "foo", ["bar"], encoding); } -} -@suite class Id3v2_UserInformationFrame_PropertyTests { @test - public setDescription() { - // Arrange - const frame = getTestFrame(); - + public fromFields_noParams() { // Act - frame.description = "fux"; + const frame = UserTextInformationFrame.fromFields(); // Assert - assert.strictEqual(frame.description, "fux"); - assert.deepStrictEqual(frame.text, ["bar"]); + assertFrame(frame, "", [], Id3v2Settings.defaultEncoding); } - @params(undefined, "undefined") - @params(null, "null") - @params("", "empty_string") - @params("fux", "truthy") - public setDescription_values(value: string) { - // Arrange - const frame = UserTextInformationFrame.fromDescription("foo"); - frame.text = ["bar"]; - + @test + public fromFields_withDescription() { // Act - frame.description = value; + const frame = UserTextInformationFrame.fromFields("foo"); // Assert - assert.strictEqual(frame.description, value); - assert.deepStrictEqual(frame.text, ["bar"]); + assertFrame(frame, "foo", [], Id3v2Settings.defaultEncoding); } @test - public getText() { - // Arrange - const frame = getTestFrame(); - + public fromFields_withDescriptionText() { // Act - const text = frame.text; - text.push("fux"); + const frame = UserTextInformationFrame.fromFields("foo", ["bar", "baz"]); - // Assert - new item was not added to frame - assert.notEqual(frame.text, text); - assert.strictEqual(1, frame.text.length); + // Assert + assertFrame(frame, "foo", ["bar", "baz"], Id3v2Settings.defaultEncoding); } @test - public setText() { + public fromFields_withDescriptionTextEncoding() { + // Act + const frame = UserTextInformationFrame.fromFields("foo", ["bar", "baz"], StringType.Hex); + + // Assert + assertFrame(frame, "foo", ["bar", "baz"], StringType.Hex); + } +} + +@suite class Id3v2_UserInformationFrame_PropertyTests { + @params(undefined, "undefined") + @params(null, "null") + @params("", "empty_string") + @params("fux", "truthy") + public setDescription_values(value: string) { // Arrange - const frame = getTestFrame(); + const frame = UserTextInformationFrame.fromFields("foo", ["bar"]); // Act - frame.text = ["bux", "qux"]; + frame.description = value; // Assert - assert.strictEqual(frame.description, "foo"); - assert.deepStrictEqual(frame.text, ["bux", "qux"]); + assert.strictEqual(frame.description, value); + assert.deepStrictEqual(frame.text, ["bar"]); } @params(undefined, "undefined") @@ -233,7 +198,7 @@ const getTestFrame = (): UserTextInformationFrame => { @params(["bux"], "truthy") public setText_values(value: string[]) { // Arrange - const frame = UserTextInformationFrame.fromDescription("foo"); + const frame = UserTextInformationFrame.fromFields("foo"); // Act frame.text = value; @@ -246,7 +211,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public setEncoding() { // Arrange - const frame = UserTextInformationFrame.fromDescription("foo"); + const frame = UserTextInformationFrame.fromFields("foo"); // Act frame.textEncoding = StringType.UTF8; @@ -279,8 +244,8 @@ const getTestFrame = (): UserTextInformationFrame => { @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -294,8 +259,8 @@ const getTestFrame = (): UserTextInformationFrame => { @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UserTextInformationFrame.fromDescription("foo"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UserTextInformationFrame.fromFields("foo"); const frames = [frame1, frame2]; // Act @@ -309,9 +274,9 @@ const getTestFrame = (): UserTextInformationFrame => { @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UserTextInformationFrame.fromDescription("foo"); - const frame3 = UserTextInformationFrame.fromDescription("bar"); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UserTextInformationFrame.fromFields("foo"); + const frame3 = UserTextInformationFrame.fromFields("bar"); const frames = [frame1, frame2, frame3]; @@ -326,8 +291,8 @@ const getTestFrame = (): UserTextInformationFrame => { @test public filterFrames_allMatches() { // Arrange - const frame1 = UserTextInformationFrame.fromDescription("foo"); - const frame2 = UserTextInformationFrame.fromDescription("bar"); + const frame1 = UserTextInformationFrame.fromFields("foo"); + const frame2 = UserTextInformationFrame.fromFields("bar"); const frames = [frame1, frame2]; // Act @@ -341,7 +306,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public clone_returnsCopy() { // Arrange - const frame = getTestFrame(); + const frame = UserTextInformationFrame.fromFields("foo", ["bar", "baz"], StringType.Hex); // Act const output = frame.clone(); @@ -353,8 +318,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public render_usesDescriptionAndTextFields() { // Arrange - const frame = UserTextInformationFrame.fromDescription("foo", StringType.Latin1); - frame.text = ["bar", "baz"]; + const frame = UserTextInformationFrame.fromFields("foo", ["bar", "baz"], StringType.Latin1); // Act const result = frame.render(4); @@ -379,7 +343,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public render_withoutDescriptionWithoutText() { // Arrange - const frame = UserTextInformationFrame.fromDescription(undefined, StringType.Latin1); + const frame = UserTextInformationFrame.fromFields(undefined, undefined, StringType.Latin1); // Act const result = frame.render(4); @@ -392,8 +356,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public render_withoutDescriptionWithText() { // Arrange - const frame = UserTextInformationFrame.fromDescription(undefined, StringType.Latin1); - frame.text = ["foo"]; + const frame = UserTextInformationFrame.fromFields(undefined, ["foo"], StringType.Latin1); // Act const result = frame.render(4); @@ -415,7 +378,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public render_withDescriptionWithoutText() { // Arrange - const frame = UserTextInformationFrame.fromDescription("foo", StringType.Latin1); + const frame = UserTextInformationFrame.fromFields("foo", undefined, StringType.Latin1); // Act const result = frame.render(4); @@ -437,7 +400,7 @@ const getTestFrame = (): UserTextInformationFrame => { @test public render_withDescriptionWithText_twoByteEncoding() { // Arrange - const frame = UserTextInformationFrame.fromDescription("foo", StringType.UTF16LE); + const frame = UserTextInformationFrame.fromFields("foo", ["bar"], StringType.UTF16LE); frame.text = ["bar"]; // Act @@ -464,8 +427,7 @@ const getTestFrame = (): UserTextInformationFrame => { @params(["foo", ["bar"]], "foo_bar") public toString_returnsText([description, text]: [string, string[]]) { // Arrange - const frame = UserTextInformationFrame.fromDescription(description); - frame.text = text; + const frame = UserTextInformationFrame.fromFields(description, text); // Act const result = frame.toString(); diff --git a/test-unit/id3v2/userUrlLinkFrameTests.ts b/test-unit/id3v2/userUrlLinkFrameTests.ts index 9b077dab..22398d8a 100644 --- a/test-unit/id3v2/userUrlLinkFrameTests.ts +++ b/test-unit/id3v2/userUrlLinkFrameTests.ts @@ -17,7 +17,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, assert.strictEqual(frame.frameId, FrameIdentifiers.WXXX); assert.strictEqual(frame.description, description); - assert.strictEqual(frame.text, text); + assert.strictEqual(frame.url, text); assert.equal(frame.textEncoding, encoding); } @@ -35,7 +35,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, assert.isOk(frame); assert.strictEqual(frame.frameId, FrameIdentifiers.WXXX); assert.strictEqual(frame.description, "foo"); - assert.strictEqual(frame.text, "bar"); + assert.strictEqual(frame.url, "bar"); assert.strictEqual(frame.textEncoding, Id3v2Settings.defaultEncoding); } @@ -164,7 +164,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, // Assert assert.strictEqual(frame.description, value); - assert.strictEqual(frame.text, "bar"); + assert.strictEqual(frame.url, "bar"); } @params(undefined, "undefined") @@ -176,11 +176,11 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, const frame = UserUrlLinkFrame.fromFields("foo", "bar"); // Act - frame.text = value; + frame.url= value; // Assert assert.strictEqual(frame.description, "foo"); - assert.strictEqual(frame.text, value); + assert.strictEqual(frame.url, value); } @test @@ -207,7 +207,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, const result = frame.clone(); // Assert - assertFrame(result, frame.description, frame.text, frame.textEncoding); + assertFrame(result, frame.description, frame.url, frame.textEncoding); } @test @@ -232,8 +232,8 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, @test public filterFrames_noMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); - const frame2 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(234)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); + const frame2 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frames = [frame1, frame2]; // Act @@ -247,7 +247,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, @test public filterFrames_singleMatch() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frame2 = UserUrlLinkFrame.fromFields("foo", "bar"); const frames = [frame1, frame2]; @@ -262,7 +262,7 @@ const assertFrame = (frame: UserUrlLinkFrame, description: string, text: string, @test public filterFrames_multipleMatches() { // Arrange - const frame1 = UnknownFrame.fromData(FrameIdentifiers.RVRB, ByteVector.fromUint(123)); + const frame1 = UnknownFrame.fromFields(FrameIdentifiers.RVRB); const frame2 = UserUrlLinkFrame.fromFields("foo", "bar"); const frame3 = UserUrlLinkFrame.fromFields("foo", "bar");