diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts new file mode 100644 index 000000000..3e8a99b7c --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -0,0 +1,162 @@ +import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessage', () => { + it('should create an instance with all properties', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_all_properties'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + campaignId: 456, + isProof: false, + }, + elements: { + title: 'Awesome Title', + body: 'Radical Body Text', + mediaUrl: 'https://example.com/image.jpg', + mediaUrlCaption: 'Check out this sick image!', + defaultAction: { + type: 'openUrl', + data: 'https://example.com', + }, + buttons: [ + { + id: 'button-1', + title: 'Click Me!', + action: { + type: 'openUrl', + data: 'https://example.com/button1', + }, + }, + ], + text: [ + { + id: 'text-1', + text: 'Some cool text', + type: 'body', + }, + ], + }, + payload: { + customKey: 'customValue', + anotherKey: 123, + }, + }; + + const message = new IterableEmbeddedMessage(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + + // Check metadata + expect(message.metadata).toBeInstanceOf(Object); + expect(message.metadata.messageId).toBe('msg-123'); + expect(message.metadata.placementId).toBe(1); + expect(message.metadata.campaignId).toBe(456); + expect(message.metadata.isProof).toBe(false); + + // Check elements + expect(message.elements).toBeInstanceOf(Object); + expect(message.elements?.title).toBe('Awesome Title'); + expect(message.elements?.body).toBe('Radical Body Text'); + expect(message.elements?.mediaUrl).toBe('https://example.com/image.jpg'); + expect(message.elements?.mediaUrlCaption).toBe( + 'Check out this sick image!' + ); + + // Check payload + expect(message.payload).toEqual({ + customKey: 'customValue', + anotherKey: 123, + }); + }); + + it('should create an instance with only required metadata', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_required_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + }; + + const message = new IterableEmbeddedMessage(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(Object); + expect(message.metadata.messageId).toBe('msg-123'); + expect(message.metadata.placementId).toBe(1); + expect(message.metadata.campaignId).toBeUndefined(); + expect(message.metadata.isProof).toBe(false); + expect(message.elements).toBeUndefined(); + expect(message.payload).toBeUndefined(); + }); + + it('should throw an error if metadata is missing', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_missing_metadata'); + + const dict = { + elements: { + title: 'Some Title', + body: 'Some Body', + }, + }; + + // @ts-expect-error - metadata is purposely missing + expect(() => new IterableEmbeddedMessage(dict)).toThrow( + 'metadata is required' + ); + }); + + it('should create an instance with elements but no payload', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_elements_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + elements: { + title: 'Elements Only', + body: 'No payload here', + }, + }; + + const message = new IterableEmbeddedMessage(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(Object); + expect(message.elements).toBeInstanceOf(Object); + expect(message.elements?.title).toBe('Elements Only'); + expect(message.elements?.body).toBe('No payload here'); + expect(message.payload).toBeUndefined(); + }); + + it('should create an instance with payload but no elements', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_payload_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + payload: { + someData: 'someValue', + }, + }; + + const message = new IterableEmbeddedMessage(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(Object); + expect(message.elements).toBeUndefined(); + expect(message.payload).toEqual({ + someData: 'someValue', + }); + }); +}); diff --git a/src/__tests__/IterableEmbeddedMessageButton.test.ts b/src/__tests__/IterableEmbeddedMessageButton.test.ts new file mode 100644 index 000000000..5e20f90c0 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageButton.test.ts @@ -0,0 +1,86 @@ +import { IterableEmbeddedMessageButton } from '../embedded/classes/IterableEmbeddedMessageButton'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessageButton', () => { + it('should create an instance with all properties including button action', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageButton_fromDict_all_properties' + ); + + const dict = { + id: 'button-123', + title: 'Click Me!', + action: { type: 'openUrl', data: 'https://example.com' }, + }; + + const button = new IterableEmbeddedMessageButton(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBe('Click Me!'); + expect(button.action).toBeInstanceOf(Object); + expect(button.action?.type).toBe('openUrl'); + expect(button.action?.data).toBe('https://example.com'); + }); + + it('should create an instance with only required properties', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_required_only'); + + const dict = { id: 'button-123' }; + + const button = new IterableEmbeddedMessageButton(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBeUndefined(); + expect(button.action).toBeUndefined(); + }); + + it('should create an instance with title but no action', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_title_only'); + + const dict = { + id: 'button-123', + title: 'Click Me!', + }; + + const button = new IterableEmbeddedMessageButton(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBe('Click Me!'); + expect(button.action).toBeUndefined(); + }); + + it('should throw an error if id is missing', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_missing_id'); + + const dict = { + title: 'Click Me!', + action: { type: 'openUrl', data: 'https://example.com' }, + }; + // @ts-expect-error - id is purposely missing + expect(() => new IterableEmbeddedMessageButton(dict)).toThrow( + 'id is required' + ); + }); + + it('should handle button action with only type', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageButton_fromDict_action_type_only' + ); + + const dict = { + id: 'button-123', + action: { type: 'close' }, + }; + + const button = new IterableEmbeddedMessageButton(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.action).toBeInstanceOf(Object); + expect(button.action?.type).toBe('close'); + expect(button.action?.data).toBeUndefined(); + }); +}); diff --git a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts deleted file mode 100644 index f57786a9d..000000000 --- a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; -import { Iterable } from '../core'; - -describe('IterableEmbeddedMessage', () => { - test('should create an instance of IterableEmbeddedMessageMetadata from a dictionary', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageMetadata_fromDict_valid_dictionary' - ); - - const dict = { - messageId: '123', - placementId: 456, - campaignId: 789, - isProof: false, - }; - - const result = IterableEmbeddedMessageMetadata.fromDict(dict); - - expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); - expect(result.messageId).toBe('123'); - expect(result.placementId).toBe(456); - expect(result.campaignId).toBe(789); - expect(result.isProof).toBe(false); - }); - - test('should handle optional fields', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageMetadata_fromDict_optional_fields_omitted' - ); - - const dict = { - messageId: '123', - placementId: 456, - }; - - const result = IterableEmbeddedMessageMetadata.fromDict(dict); - - expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); - expect(result.messageId).toBe('123'); - expect(result.placementId).toBe(456); - expect(result.campaignId).toBeUndefined(); - expect(result.isProof).toBe(false); - }); - - test('should throw an error if messageId is not provided', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageMetadata_fromDict_missing_messageId' - ); - - const dict = { - placementId: 456, - }; - - expect(() => { - IterableEmbeddedMessageMetadata.fromDict(dict); - }).toThrow('messageId and placementId are required'); - }); -}); diff --git a/src/__tests__/IterableEmbeddedMessageText.test.ts b/src/__tests__/IterableEmbeddedMessageText.test.ts new file mode 100644 index 000000000..2ac677d55 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageText.test.ts @@ -0,0 +1,39 @@ +import { IterableEmbeddedMessageText } from '../embedded/classes/IterableEmbeddedMessageText'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessageText', () => { + it('should create an instance from a dictionary with all properties', () => { + Iterable.logger.log('iterableEmbeddedMessageText_fromDict_all_properties'); + + const dict = { id: 'text-123', text: 'Hello World!', type: 'heading' }; + const text = new IterableEmbeddedMessageText(dict); + + expect(text).toBeInstanceOf(IterableEmbeddedMessageText); + expect(text.id).toBe('text-123'); + expect(text.text).toBe('Hello World!'); + expect(text.type).toBe('heading'); + }); + + it('should create an instance from a dictionary with only required properties', () => { + Iterable.logger.log('iterableEmbeddedMessageText_fromDict_required_only'); + + const dict = { id: 'text-123' }; + const text = new IterableEmbeddedMessageText(dict); + + expect(text).toBeInstanceOf(IterableEmbeddedMessageText); + expect(text.id).toBe('text-123'); + expect(text.text).toBeUndefined(); + expect(text.type).toBeUndefined(); + }); + + it('should throw an error if id is missing in fromDict', () => { + Iterable.logger.log('iterableEmbeddedMessageText_fromDict_missing_id'); + + const dict = { text: 'Hello World!', type: 'heading' }; + + // @ts-expect-error - id is purposely missing + expect(() => new IterableEmbeddedMessageText(dict)).toThrow( + 'id is required' + ); + }); +}); diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..9d528504d --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -0,0 +1,131 @@ +import { IterableEmbeddedMessageButton } from './IterableEmbeddedMessageButton'; +import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; + +/** + * IterableEmbeddedMessage represents an embedded message. + */ +export class IterableEmbeddedMessage { + /** The metadata of the embedded message */ + readonly metadata: { + /** The id of the message */ + messageId: string; + /** The placement id of the message */ + placementId: number; + /** The campaign id of the message */ + campaignId?: number; + /** Whether the message is a proof */ + isProof: boolean; + }; + /** The elements of the embedded message */ + readonly elements?: { + /** The title of the embedded message */ + title?: string; + /** The body of the embedded message */ + body?: string; + /** The media url of the embedded message */ + mediaUrl?: string; + /** The media url caption of the embedded message */ + mediaUrlCaption?: string; + /** The default action of the embedded message */ + defaultAction?: { + /** The type of action */ + type: string; + /** The url for the action when the type is `openUrl` */ + data?: string; + }; + /** The buttons of the embedded message */ + buttons?: IterableEmbeddedMessageButton[]; + /** The text of the embedded message */ + text?: IterableEmbeddedMessageText[]; + }; + /** The custom payload of the embedded message */ + readonly payload?: Record; + + /** + * Creates an instance of `IterableEmbeddedMessage`. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessage` instance. + */ + constructor(dict: EmbeddedMessageDict) { + if (!dict.metadata) { + throw new Error('metadata is required'); + } + this.metadata = { + messageId: dict.metadata.messageId, + placementId: dict.metadata.placementId, + campaignId: dict.metadata.campaignId, + isProof: dict.metadata.isProof, + }; + + if (dict.elements) { + this.elements = { + title: dict.elements?.title, + body: dict.elements?.body, + mediaUrl: dict.elements?.mediaUrl, + mediaUrlCaption: dict.elements?.mediaUrlCaption, + }; + + if (dict.elements?.defaultAction) { + this.elements.defaultAction = { + type: dict.elements.defaultAction.type, + data: dict.elements.defaultAction.data, + }; + } + + if (dict.elements?.buttons) { + this.elements.buttons = dict.elements.buttons.map( + (button) => new IterableEmbeddedMessageButton(button) + ); + } + + if (dict.elements?.text) { + this.elements.text = dict.elements.text.map( + (text) => new IterableEmbeddedMessageText(text) + ); + } + } + + this.payload = dict.payload; + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message. + */ +export interface EmbeddedMessageDict { + /** The metadata of the embedded message */ + metadata: { + /** The id of the message */ + messageId: string; + /** The placement id of the message */ + placementId: number; + /** The campaign id of the message */ + campaignId?: number; + /** Whether the message is a proof */ + isProof: boolean; + }; + /** The elements of the embedded message */ + elements?: { + /** The title of the embedded message */ + title?: string; + /** The body of the embedded message */ + body?: string; + /** The media url of the embedded message */ + mediaUrl?: string; + /** The media url caption of the embedded message */ + mediaUrlCaption?: string; + /** The default action of the embedded message */ + defaultAction?: { + /** The type of action */ + type: string; + /** The url for the action when the type is `openUrl` */ + data?: string; + }; + /** The buttons of the embedded message */ + buttons?: IterableEmbeddedMessageButton[]; + /** The text of the embedded message */ + text?: IterableEmbeddedMessageText[]; + }; + /** The custom payload of the embedded message */ + payload?: Record; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts new file mode 100644 index 000000000..9a0896d96 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -0,0 +1,54 @@ +/** + * IterableEmbeddedMessageElementsButton represents a button in an embedded message. + */ +export class IterableEmbeddedMessageButton { + /** The ID for the embedded message button */ + readonly id: string; + /** The title for the embedded message button */ + readonly title?: string; + /** The action for the embedded message button */ + readonly action?: { + /** The type of action */ + type: string; + /** The url for the action when the type is `openUrl` */ + data?: string; + }; + + /** + * Creates an instance of IterableEmbeddedMessageButton. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageButton` instance. + */ + constructor(dict: EmbeddedMessageElementsButtonDict) { + if (!dict.id) { + throw new Error('id is required'); + } + + this.id = dict.id; + this.title = dict.title; + + if (dict.action) { + this.action = { + type: dict.action.type, + data: dict.action.data, + }; + } + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message button. + */ +export interface EmbeddedMessageElementsButtonDict { + /** The ID for the embedded message button */ + id: string; + /** The title for the embedded message button */ + title?: string; + /** The action for the embedded message button */ + action?: { + /** The type of action */ + type: string; + /** The url for the action when the type is `openUrl` */ + data?: string; + }; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts deleted file mode 100644 index 02bc032d0..000000000 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Metadata for an embedded message. - */ -export class IterableEmbeddedMessageMetadata { - /** The ID for the embedded message */ - readonly messageId: string; - /** The placement ID for the embedded message */ - readonly placementId: number; - /** The campaign ID for the embedded message */ - readonly campaignId?: number; - /** Whether the embedded message is a proof */ - readonly isProof: boolean; - - /** - * Constructs an instance of IterableEmbeddedMessageMetadata. - * - * @param messageId - The ID for the embedded message. - * @param placementId - The placement ID for the embedded message. - * @param campaignId - The campaign ID for the embedded message. - * @param isProof - Whether the embedded message is a proof. - */ - constructor( - messageId: string, - placementId: number, - campaignId: number | undefined, - isProof: boolean = false - ) { - this.messageId = messageId; - this.placementId = placementId; - this.campaignId = campaignId; - this.isProof = isProof; - } - - /** - * Creates an instance of `IterableEmbeddedMessageMetadata` from a dictionary object. - * - * @param dict - The dictionary objectcontaining the metadata properties. - * This corresponds to the properties in {@link IterableEmbeddedMessageMetadata} - * - * @returns A new instance of `IterableEmbeddedMessageMetadata` with the provided properties. - */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageMetadata { - if (!dict.messageId || !dict.placementId) { - throw new Error('messageId and placementId are required'); - } - return new IterableEmbeddedMessageMetadata( - dict.messageId, - dict.placementId, - dict.campaignId, - dict.isProof - ); - } -} - -/** - * An interface defining the dictionary object containing the metadata properties for an embedded message. - */ -export interface EmbeddedMessageMetadataDict { - messageId: string; - placementId: number; - campaignId?: number; - isProof?: boolean; -} diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts new file mode 100644 index 000000000..3b88dc8a8 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -0,0 +1,40 @@ +/** + * IterableEmbeddedMessageText represents a text element in an embedded message. + */ +export class IterableEmbeddedMessageText { + /** The id of the text element */ + readonly id: string; + /** The text of the text element */ + readonly text?: string; + /** The type of the text element */ + readonly type?: string; + + /** + * Creates an instance of `IterableEmbeddedMessageText`. + * + * @param id - The id of the text element + * @param text - The text of the text element + * @param type - The type of the text element + */ + constructor(dict: EmbeddedMessageTextDict) { + if (!dict.id) { + throw new Error('id is required'); + } + + this.id = dict.id; + this.text = dict.text; + this.type = dict.type; + } +} + +/** + * An interface defining the dictionary object containing the properties for an embedded message text. + */ +export interface EmbeddedMessageTextDict { + /** The id of the text element */ + id: string; + /** The text of the text element */ + text?: string; + /** The type of the text element */ + type?: string; +} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts index 9b605f651..2a2abba63 100644 --- a/src/embedded/classes/index.ts +++ b/src/embedded/classes/index.ts @@ -1,2 +1,5 @@ export * from './IterableEmbeddedManager'; export * from './IterableEmbeddedPlacement'; +export * from './IterableEmbeddedMessage'; +export * from './IterableEmbeddedMessageButton'; +export * from './IterableEmbeddedMessageText';