From 1c56ff1efa6db2b3635c1bec2a71ca1e9f05049a Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Mon, 9 Jun 2025 11:30:49 -0600 Subject: [PATCH 01/16] feat: adds button class and associated classes --- .../IterableEmbeddedMessageElementsButton.ts | 61 +++++++++++++++++++ ...ableEmbeddedMessageElementsButtonAction.ts | 54 ++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/embedded/classes/IterableEmbeddedMessageElementsButton.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts new file mode 100644 index 000000000..b1481c9e6 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts @@ -0,0 +1,61 @@ +import { IterableEmbeddedMessageElementsButtonAction } from './IterableEmbeddedMessageElementsButtonAction'; + +/** + * IterableEmbeddedMessageElementsButton represents a button in an embedded message. + */ +export class IterableEmbeddedMessageElementsButton { + /** 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?: IterableEmbeddedMessageElementsButtonAction; + + /** + * Creates an instance of IterableEmbeddedMessageButton. + * + * @param id - The ID for the embedded message button. + * @param title - The title for the embedded message button. + * @param action - The action for the embedded message button. + */ + constructor( + id: string, + title?: string, + action?: IterableEmbeddedMessageElementsButtonAction + ) { + this.id = id; + this.title = title; + this.action = action; + } + + /** + * Creates an instance of `IterableEmbeddedMessageButton` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageButton` instance. + * @returns A new instance of `IterableEmbeddedMessageButton` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageElementsButton { + if (!dict.id) { + throw new Error('id is required'); + } + const action = dict.action + ? IterableEmbeddedMessageElementsButtonAction.fromDict(dict.action) + : undefined; + return new IterableEmbeddedMessageElementsButton( + dict.id, + dict.title, + action + ); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message button. + */ +export interface EmbeddedMessageElementsButtonDict { + id: string; + title?: string; + action?: IterableEmbeddedMessageElementsButtonAction; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts new file mode 100644 index 000000000..c8dd24708 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts @@ -0,0 +1,54 @@ +/** + * IterableEmbeddedMessageElementsButtonAction represents an action defined as a response to user events + * for an embedded message button. + */ +export class IterableEmbeddedMessageElementsButtonAction { + /** + * The type of iterable action + * For custom actions, the type is `action://` prefix followed by a custom action name + */ + readonly type: string; + + /** + * The url for the action when the type is `openUrl` + * For custom actions, data is empty + */ + readonly data?: string; + + /** + * Creates an instance of IterableEmbeddedMessageElementsButtonAction. + * + * @param type - The type of the action. + * @param data - Optional data associated with the action. + */ + constructor(type: string, data?: string) { + this.type = type; + this.data = data; + } + + /** + * Creates an instance of `IterableEmbeddedMessageElementsButtonAction` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageElementsButtonAction` instance. + * @returns A new instance of `IterableEmbeddedMessageElementsButtonAction` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageElementsButtonAction { + if (!dict.type) { + throw new Error('type is required'); + } + return new IterableEmbeddedMessageElementsButtonAction( + dict.type, + dict.data + ); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message button action. + */ +export interface EmbeddedMessageButtonActionDict { + type: string; + data?: string; +} From 4578c011eccdb6b41e587dfaf28575ec8967dbfe Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Mon, 9 Jun 2025 11:38:24 -0600 Subject: [PATCH 02/16] feat: adds unit tests --- ...rableEmbeddedMessageElementsButton.test.ts | 91 +++++++++++++++++++ ...mbeddedMessageElementsButtonAction.test.ts | 40 ++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessageElementsButton.test.ts create mode 100644 src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts diff --git a/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts b/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts new file mode 100644 index 000000000..1524d1c5b --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts @@ -0,0 +1,91 @@ +import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; +import { IterableEmbeddedMessageElementsButtonAction } from '../embedded/classes/IterableEmbeddedMessageElementsButtonAction'; +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 = IterableEmbeddedMessageElementsButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBe('Click Me!'); + expect(button.action).toBeInstanceOf( + IterableEmbeddedMessageElementsButtonAction + ); + 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 = IterableEmbeddedMessageElementsButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + 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 = IterableEmbeddedMessageElementsButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + 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 in fromDict', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_missing_id'); + + const dict = { + title: 'Click Me!', + action: { type: 'openUrl', data: 'https://example.com' }, + }; + + expect(() => IterableEmbeddedMessageElementsButton.fromDict(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 = IterableEmbeddedMessageElementsButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button.id).toBe('button-123'); + expect(button.action).toBeInstanceOf( + IterableEmbeddedMessageElementsButtonAction + ); + expect(button.action?.type).toBe('close'); + expect(button.action?.data).toBeUndefined(); + }); +}); diff --git a/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts b/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts new file mode 100644 index 000000000..fa172cb7c --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts @@ -0,0 +1,40 @@ +import { IterableEmbeddedMessageElementsButtonAction } from '../embedded/classes/IterableEmbeddedMessageElementsButtonAction'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessageDefaultAction', () => { + it('should create an instance with the correct properties', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElementsButtonAction_fromDict_valid_dictionary' + ); + + const dict = { type: 'openUrl', data: 'https://example.com' }; + const action = IterableEmbeddedMessageElementsButtonAction.fromDict(dict); + expect(action).toBeInstanceOf(IterableEmbeddedMessageElementsButtonAction); + expect(action.type).toBe('openUrl'); + expect(action.data).toBe('https://example.com'); + }); + + it('should create an instance from a dictionary with data omitted', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElementsButtonAction_fromDict_valid_dictionary_with_data_omitted' + ); + + const dict = { type: 'action://join', data: '' }; + const action = IterableEmbeddedMessageElementsButtonAction.fromDict(dict); + expect(action).toBeInstanceOf(IterableEmbeddedMessageElementsButtonAction); + expect(action.type).toBe('action://join'); + expect(action.data).toBe(''); + }); + + it('should throw an error if type is missing in fromDict', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElementsButtonAction_fromDict_invalid_dictionary_missing_type' + ); + + const dict = { data: 'foo' }; + + expect(() => + IterableEmbeddedMessageElementsButtonAction.fromDict(dict) + ).toThrow('type is required'); + }); +}); From 57f48a3ff875fc8739d062a84180e5c64ad69a8f Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Mon, 9 Jun 2025 12:11:02 -0600 Subject: [PATCH 03/16] feat: adds elements class and associated classes --- .../IterableEmbeddedMessageDefaultAction.ts | 51 +++++++++ .../IterableEmbeddedMessageElements.ts | 101 ++++++++++++++++++ ...ableEmbeddedMessageElementsButtonAction.ts | 4 +- .../classes/IterableEmbeddedMessageText.ts | 48 +++++++++ src/embedded/classes/index.ts | 3 + 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageElements.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageText.ts diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts new file mode 100644 index 000000000..01275c56d --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -0,0 +1,51 @@ +/** + * IterableEmbeddedMessageDefaultAction represents the default action defined as + * a response to user events for an embedded message + */ +export class IterableEmbeddedMessageDefaultAction { + /** + * The type of iterable action + * For custom actions, the type is `action://` prefix followed by a custom action name + */ + readonly type: string; + + /** + * The url for the action when the type is `openUrl` + * For custom actions, data is empty + */ + readonly data?: string; + + /** + * Creates an instance of `IterableEmbeddedMessageDefaultAction`. + * + * @param type - The type of iterable action + * @param data - The url for the action when the type is `openUrl` + */ + constructor(type: string, data?: string) { + this.type = type; + this.data = data; + } + + /** + * Creates an instance of `IterableEmbeddedMessageDefaultAction` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageDefaultAction` instance. + * @returns A new instance of `IterableEmbeddedMessageDefaultAction` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageDefaultAction { + if (!dict.type) { + throw new Error('type is required'); + } + return new IterableEmbeddedMessageDefaultAction(dict.type, dict.data); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message default action. + */ +export interface EmbeddedMessageDefaultActionDict { + type: string; + data?: string; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts new file mode 100644 index 000000000..eeb8887c9 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -0,0 +1,101 @@ +import { IterableEmbeddedMessageDefaultAction } from './IterableEmbeddedMessageDefaultAction'; +import { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMessageElementsButton'; +import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; + +/** + * IterableEmbeddedMessageElements represents the elements of an embedded message. + */ +export class IterableEmbeddedMessageElements { + /** The title of the embedded message */ + readonly title?: string; + /** The body of the embedded message */ + readonly body?: string; + /** The url of the embedded message image */ + readonly mediaUrl?: string; + /** The caption of the embedded message image */ + readonly mediaUrlCaption?: string; + /** The default action of the embedded message */ + readonly defaultAction?: IterableEmbeddedMessageDefaultAction; + /** The buttons of the embedded message */ + readonly buttons?: IterableEmbeddedMessageElementsButton[]; + /** The text elements of the embedded message */ + readonly text?: IterableEmbeddedMessageText[]; + + /** + * Creates an instance of `IterableEmbeddedMessageElements`. + * + * @param title - The title of the embedded message. + * @param body - The body of the embedded message. + * @param mediaUrl - The url of the embedded message image. + * @param mediaUrlCaption - The caption of the embedded message image. + * @param defaultAction - The default action of the embedded message. + * @param buttons - The buttons of the embedded message. + * @param text - The text elements of the embedded message. + */ + constructor( + title?: string, + body?: string, + mediaUrl?: string, + mediaUrlCaption?: string, + defaultAction?: IterableEmbeddedMessageDefaultAction, + buttons?: IterableEmbeddedMessageElementsButton[], + text?: IterableEmbeddedMessageText[] + ) { + this.title = title; + this.body = body; + this.mediaUrl = mediaUrl; + this.mediaUrlCaption = mediaUrlCaption; + this.defaultAction = defaultAction; + this.buttons = buttons; + this.text = text; + } + + /** + * Creates an instance of `IterableEmbeddedMessageElements` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageElements` instance. + * @returns A new instance of `IterableEmbeddedMessageElements` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageElements { + const title = dict.title; + const body = dict.body; + const mediaUrl = dict.mediaUrl; + const mediaUrlCaption = dict.mediaUrlCaption; + const defaultAction = dict.defaultAction + ? IterableEmbeddedMessageDefaultAction.fromDict(dict.defaultAction) + : undefined; + + const buttons = dict.buttons?.map((button) => + IterableEmbeddedMessageElementsButton.fromDict(button) + ); + + const text = dict.text?.map((text) => + IterableEmbeddedMessageText.fromDict(text) + ); + + return new IterableEmbeddedMessageElements( + title, + body, + mediaUrl, + mediaUrlCaption, + defaultAction, + buttons, + text + ); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message elements. + */ +export interface EmbeddedMessageElementsDict { + title?: string; + body?: string; + mediaUrl?: string; + mediaUrlCaption?: string; + defaultAction?: IterableEmbeddedMessageDefaultAction; + buttons?: IterableEmbeddedMessageElementsButton[]; + text?: IterableEmbeddedMessageText[]; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts index c8dd24708..a924ffec5 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts @@ -1,6 +1,6 @@ /** - * IterableEmbeddedMessageElementsButtonAction represents an action defined as a response to user events - * for an embedded message button. + * IterableEmbeddedMessageElementsButtonAction represents an action defined as + * a response to user events for an embedded message button */ export class IterableEmbeddedMessageElementsButtonAction { /** diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts new file mode 100644 index 000000000..d8a878519 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -0,0 +1,48 @@ +/** + * 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(id: string, text?: string, type?: string) { + this.id = id; + this.text = text; + this.type = type; + } + + /** + * Creates an instance of `IterableEmbeddedMessageText` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageText` instance. + * @returns A new instance of `IterableEmbeddedMessageText` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageText { + if (!dict.id) { + throw new Error('id is required'); + } + return new IterableEmbeddedMessageText(dict.id, dict.text, dict.type); + } +} + +/** + * An interface defining the dictionary object containing the properties for an embedded message text. + */ +export interface EmbeddedMessageTextDict { + id: string; + text?: string; + type?: string; +} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts index 9b605f651..3b0ab3fa8 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 './IterableEmbeddedMessageElementsButton'; +export * from './IterableEmbeddedMessageElementsButtonAction'; +export * from './IterableEmbeddedMessageMetadata'; From a3397a4335f474f7c95ceb9ba3ddf7a2e40f2d96 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Mon, 9 Jun 2025 12:14:47 -0600 Subject: [PATCH 04/16] feat: adds unit tests --- ...erableEmbeddedMessageDefaultAction.test.ts | 40 ++++ .../IterableEmbeddedMessageElements.test.ts | 214 ++++++++++++++++++ .../IterableEmbeddedMessageText.test.ts | 38 ++++ 3 files changed, 292 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts create mode 100644 src/__tests__/IterableEmbeddedMessageElements.test.ts create mode 100644 src/__tests__/IterableEmbeddedMessageText.test.ts diff --git a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts new file mode 100644 index 000000000..bf99bc5b8 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts @@ -0,0 +1,40 @@ +import { IterableEmbeddedMessageDefaultAction } from '../embedded/classes/IterableEmbeddedMessageDefaultAction'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessageDefaultAction', () => { + it('should create an instance with the correct properties', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageDefaultAction_fromDict_valid_dictionary' + ); + + const dict = { type: 'openUrl', data: 'https://example.com' }; + const action = IterableEmbeddedMessageDefaultAction.fromDict(dict); + expect(action).toBeInstanceOf(IterableEmbeddedMessageDefaultAction); + expect(action.type).toBe('openUrl'); + expect(action.data).toBe('https://example.com'); + }); + + it('should create an instance from a dictionary with data omitted', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageDefaultAction_fromDict_valid_dictionary_with_data_omitted' + ); + + const dict = { type: 'action://join', data: '' }; + const action = IterableEmbeddedMessageDefaultAction.fromDict(dict); + expect(action).toBeInstanceOf(IterableEmbeddedMessageDefaultAction); + expect(action.type).toBe('action://join'); + expect(action.data).toBe(''); + }); + + it('should throw an error if type is missing in fromDict', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageDefaultAction_fromDict_invalid_dictionary_missing_type' + ); + + const dict = { data: 'foo' }; + + expect(() => IterableEmbeddedMessageDefaultAction.fromDict(dict)).toThrow( + 'type is required' + ); + }); +}); diff --git a/src/__tests__/IterableEmbeddedMessageElements.test.ts b/src/__tests__/IterableEmbeddedMessageElements.test.ts new file mode 100644 index 000000000..00028da5f --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageElements.test.ts @@ -0,0 +1,214 @@ +import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; +import { IterableEmbeddedMessageDefaultAction } from '../embedded/classes/IterableEmbeddedMessageDefaultAction'; +import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; +import { IterableEmbeddedMessageText } from '../embedded/classes/IterableEmbeddedMessageText'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessageElements', () => { + it('should create an instance with all properties', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_all_properties' + ); + + const dict = { + 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', + }, + }, + { + id: 'button-2', + title: 'Close', + action: { + type: 'action://dismiss', + }, + }, + ], + text: [ + { + id: 'text-1', + text: 'Some cool text', + type: 'body', + }, + { + id: 'text-2', + text: 'More radical text', + type: 'subtitle', + }, + ], + }; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBe('Awesome Title'); + expect(elements.body).toBe('Radical Body Text'); + expect(elements.mediaUrl).toBe('https://example.com/image.jpg'); + expect(elements.mediaUrlCaption).toBe('Check out this sick image!'); + + // Check defaultAction + expect(elements.defaultAction).toBeInstanceOf( + IterableEmbeddedMessageDefaultAction + ); + expect(elements.defaultAction?.type).toBe('openUrl'); + expect(elements.defaultAction?.data).toBe('https://example.com'); + + // Check buttons + expect(elements.buttons).toHaveLength(2); + const firstButton = elements + .buttons![0] as IterableEmbeddedMessageElementsButton; + expect(firstButton).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(firstButton.id).toBe('button-1'); + expect(firstButton.title).toBe('Click Me!'); + expect(firstButton.action?.type).toBe('openUrl'); + expect(firstButton.action?.data).toBe('https://example.com/button1'); + + const secondButton = elements + .buttons![1] as IterableEmbeddedMessageElementsButton; + expect(secondButton).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(secondButton.id).toBe('button-2'); + expect(secondButton.title).toBe('Close'); + expect(secondButton.action?.type).toBe('action://dismiss'); + expect(secondButton.action?.data).toBeUndefined(); + + // Check text elements + expect(elements.text).toHaveLength(2); + const firstText = elements.text![0] as IterableEmbeddedMessageText; + expect(firstText).toBeInstanceOf(IterableEmbeddedMessageText); + expect(firstText.id).toBe('text-1'); + expect(firstText.text).toBe('Some cool text'); + expect(firstText.type).toBe('body'); + + const secondText = elements.text![1] as IterableEmbeddedMessageText; + expect(secondText).toBeInstanceOf(IterableEmbeddedMessageText); + expect(secondText.id).toBe('text-2'); + expect(secondText.text).toBe('More radical text'); + expect(secondText.type).toBe('subtitle'); + }); + + it('should create an instance with title and body', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_title_and_body' + ); + + const dict = { + title: 'Simple Title', + body: 'Simple Body', + }; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBe('Simple Title'); + expect(elements.body).toBe('Simple Body'); + expect(elements.mediaUrl).toBeUndefined(); + expect(elements.mediaUrlCaption).toBeUndefined(); + expect(elements.defaultAction).toBeUndefined(); + expect(elements.buttons).toBeUndefined(); + expect(elements.text).toBeUndefined(); + }); + + it('should create an instance with no title or body', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_no_title_or_body' + ); + + const dict = {}; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBeUndefined(); + expect(elements.body).toBeUndefined(); + expect(elements.mediaUrl).toBeUndefined(); + expect(elements.mediaUrlCaption).toBeUndefined(); + expect(elements.defaultAction).toBeUndefined(); + expect(elements.buttons).toBeUndefined(); + expect(elements.text).toBeUndefined(); + }); + + it('should create an instance with media properties', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_media_properties' + ); + + const dict = { + title: 'Media Title', + body: 'Media Body', + mediaUrl: 'https://example.com/media.jpg', + mediaUrlCaption: 'Check this out!', + }; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBe('Media Title'); + expect(elements.body).toBe('Media Body'); + expect(elements.mediaUrl).toBe('https://example.com/media.jpg'); + expect(elements.mediaUrlCaption).toBe('Check this out!'); + expect(elements.defaultAction).toBeUndefined(); + expect(elements.buttons).toBeUndefined(); + expect(elements.text).toBeUndefined(); + }); + + it('should create an instance with defaultAction only', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_defaultAction_only' + ); + + const dict = { + title: 'Action Title', + body: 'Action Body', + defaultAction: { + type: 'openUrl', + data: 'https://example.com', + }, + }; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBe('Action Title'); + expect(elements.body).toBe('Action Body'); + expect(elements.defaultAction).toBeInstanceOf( + IterableEmbeddedMessageDefaultAction + ); + expect(elements.defaultAction?.type).toBe('openUrl'); + expect(elements.defaultAction?.data).toBe('https://example.com'); + expect(elements.buttons).toBeUndefined(); + expect(elements.text).toBeUndefined(); + }); + + it('should create an instance with empty arrays for buttons and text', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageElements_fromDict_empty_arrays' + ); + + const dict = { + title: 'Empty Arrays Title', + body: 'Empty Arrays Body', + buttons: [], + text: [], + }; + + const elements = IterableEmbeddedMessageElements.fromDict(dict); + + expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(elements.title).toBe('Empty Arrays Title'); + expect(elements.body).toBe('Empty Arrays Body'); + expect(elements.buttons).toHaveLength(0); + expect(elements.text).toHaveLength(0); + }); +}); diff --git a/src/__tests__/IterableEmbeddedMessageText.test.ts b/src/__tests__/IterableEmbeddedMessageText.test.ts new file mode 100644 index 000000000..10b3e2ffe --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageText.test.ts @@ -0,0 +1,38 @@ +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 = IterableEmbeddedMessageText.fromDict(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 = IterableEmbeddedMessageText.fromDict(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' }; + + expect(() => IterableEmbeddedMessageText.fromDict(dict)).toThrow( + 'id is required' + ); + }); +}); From 79e72251b8016f48396eddaf205c1e2bcfed4b51 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Mon, 9 Jun 2025 12:48:23 -0600 Subject: [PATCH 05/16] feat: adds message class and unit tests --- src/__tests__/IterableEmbeddedMessage.test.ts | 163 ++++++++++++++++++ .../classes/IterableEmbeddedMessage.ts | 58 +++++++ 2 files changed, 221 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessage.test.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessage.ts diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts new file mode 100644 index 000000000..64385e99f --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -0,0 +1,163 @@ +import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; +import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; +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 = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + + // Check metadata + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + 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(IterableEmbeddedMessageElements); + 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 = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + 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', + }, + }; + + expect(() => IterableEmbeddedMessage.fromDict(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 = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); + 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 = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.elements).toBeUndefined(); + expect(message.payload).toEqual({ + someData: 'someValue', + }); + }); +}); diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..47adb916a --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -0,0 +1,58 @@ +import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; + +/** + * IterableEmbeddedMessage represents an embedded message. + */ +export class IterableEmbeddedMessage { + /** The metadata of the embedded message */ + metadata: IterableEmbeddedMessageMetadata; + /** The elements of the embedded message */ + elements?: IterableEmbeddedMessageElements; + /** The custom payload of the embedded message */ + payload?: Record; + + /** + * Creates an instance of `IterableEmbeddedMessage`. + * + * @param metadata - The metadata of the embedded message. + * @param elements - The elements of the embedded message. + * @param payload - The custom payload of the embedded message. + */ + constructor( + metadata: IterableEmbeddedMessageMetadata, + elements?: IterableEmbeddedMessageElements, + payload?: Record + ) { + this.metadata = metadata; + this.elements = elements; + this.payload = payload; + } + + /** + * Creates an instance of `IterableEmbeddedMessage` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessage` instance. + * @returns A new instance of `IterableEmbeddedMessage` initialized with the provided dictionary properties. + */ + static fromDict(dict: Partial): IterableEmbeddedMessage { + if (!dict.metadata) { + throw new Error('metadata is required'); + } + const metadata = IterableEmbeddedMessageMetadata.fromDict(dict.metadata); + const elements = dict.elements + ? IterableEmbeddedMessageElements.fromDict(dict.elements) + : undefined; + const payload = dict.payload; + return new IterableEmbeddedMessage(metadata, elements, payload); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message. + */ +interface EmbeddedMessageDict { + metadata: IterableEmbeddedMessageMetadata; + elements: IterableEmbeddedMessageElements; + payload: Record; +} From 80d798144d85b1a70330e1d178d9362df0009e5b Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 24 Jun 2025 13:27:26 -0600 Subject: [PATCH 06/16] feat: adds labels for dict --- src/embedded/classes/IterableEmbeddedMessageElementsButton.ts | 3 +++ .../classes/IterableEmbeddedMessageElementsButtonAction.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts index b1481c9e6..64a516a20 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts @@ -55,7 +55,10 @@ export class IterableEmbeddedMessageElementsButton { * 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?: IterableEmbeddedMessageElementsButtonAction; } diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts index c8dd24708..35283e010 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts @@ -49,6 +49,8 @@ export class IterableEmbeddedMessageElementsButtonAction { * An interface defining the dictionary object containing the properties for the embedded message button action. */ export interface EmbeddedMessageButtonActionDict { + /** The type of the action */ type: string; + /** The data associated with the action */ data?: string; } From 58fe64e25abd7d91d60562aadd74c03f7ab21532 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 24 Jun 2025 15:02:24 -0600 Subject: [PATCH 07/16] chore: adds comments to props --- .../classes/IterableEmbeddedMessageDefaultAction.ts | 2 ++ src/embedded/classes/IterableEmbeddedMessageElements.ts | 7 +++++++ src/embedded/classes/IterableEmbeddedMessageText.ts | 3 +++ 3 files changed, 12 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts index 01275c56d..06a36c00e 100644 --- a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -46,6 +46,8 @@ export class IterableEmbeddedMessageDefaultAction { * An interface defining the dictionary object containing the properties for the embedded message default action. */ export interface EmbeddedMessageDefaultActionDict { + /** The type of the action */ type: string; + /** The url for the action when the type is `openUrl` */ data?: string; } diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index eeb8887c9..73280b7e4 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -91,11 +91,18 @@ export class IterableEmbeddedMessageElements { * An interface defining the dictionary object containing the properties for the embedded message elements. */ export interface EmbeddedMessageElementsDict { + /** The title of the embedded message */ title?: string; + /** The body of the embedded message */ body?: string; + /** The url of the embedded message image */ mediaUrl?: string; + /** The caption of the embedded message image */ mediaUrlCaption?: string; + /** The default action of the embedded message */ defaultAction?: IterableEmbeddedMessageDefaultAction; + /** The buttons of the embedded message */ buttons?: IterableEmbeddedMessageElementsButton[]; + /** The text elements of the embedded message */ text?: IterableEmbeddedMessageText[]; } diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts index d8a878519..06a553476 100644 --- a/src/embedded/classes/IterableEmbeddedMessageText.ts +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -42,7 +42,10 @@ export class IterableEmbeddedMessageText { * 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; } From 84e6a6883689666b2ab64babf9564c262467a17d Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 24 Jun 2025 18:17:53 -0600 Subject: [PATCH 08/16] chore: adds comments to props --- src/embedded/classes/IterableEmbeddedMessage.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index 47adb916a..c264bf262 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -52,7 +52,10 @@ export class IterableEmbeddedMessage { * An interface defining the dictionary object containing the properties for the embedded message. */ interface EmbeddedMessageDict { + /** The metadata of the embedded message */ metadata: IterableEmbeddedMessageMetadata; + /** The elements of the embedded message */ elements: IterableEmbeddedMessageElements; + /** The custom payload of the embedded message */ payload: Record; } From cad9c3ea46c3dab3764868bccbf0d410ac48beed Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 15:24:48 -0600 Subject: [PATCH 09/16] feat: merges action into button --- ...rableEmbeddedMessageElementsButton.test.ts | 23 +++----- ...mbeddedMessageElementsButtonAction.test.ts | 40 ------------- .../IterableEmbeddedMessageElements.ts | 4 +- .../IterableEmbeddedMessageElementsButton.ts | 52 +++++++---------- ...ableEmbeddedMessageElementsButtonAction.ts | 56 ------------------- src/embedded/classes/index.ts | 1 - 6 files changed, 30 insertions(+), 146 deletions(-) delete mode 100644 src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts delete mode 100644 src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts diff --git a/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts b/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts index 1524d1c5b..b9dcf1d7b 100644 --- a/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts +++ b/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts @@ -1,5 +1,4 @@ import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; -import { IterableEmbeddedMessageElementsButtonAction } from '../embedded/classes/IterableEmbeddedMessageElementsButtonAction'; import { Iterable } from '../core/classes/Iterable'; describe('IterableEmbeddedMessageButton', () => { @@ -14,14 +13,12 @@ describe('IterableEmbeddedMessageButton', () => { action: { type: 'openUrl', data: 'https://example.com' }, }; - const button = IterableEmbeddedMessageElementsButton.fromDict(dict); + const button = new IterableEmbeddedMessageElementsButton(dict); expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); expect(button.id).toBe('button-123'); expect(button.title).toBe('Click Me!'); - expect(button.action).toBeInstanceOf( - IterableEmbeddedMessageElementsButtonAction - ); + expect(button.action).toBeInstanceOf(Object); expect(button.action?.type).toBe('openUrl'); expect(button.action?.data).toBe('https://example.com'); }); @@ -31,7 +28,7 @@ describe('IterableEmbeddedMessageButton', () => { const dict = { id: 'button-123' }; - const button = IterableEmbeddedMessageElementsButton.fromDict(dict); + const button = new IterableEmbeddedMessageElementsButton(dict); expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); expect(button.id).toBe('button-123'); @@ -47,7 +44,7 @@ describe('IterableEmbeddedMessageButton', () => { title: 'Click Me!', }; - const button = IterableEmbeddedMessageElementsButton.fromDict(dict); + const button = new IterableEmbeddedMessageElementsButton(dict); expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); expect(button.id).toBe('button-123'); @@ -55,15 +52,15 @@ describe('IterableEmbeddedMessageButton', () => { expect(button.action).toBeUndefined(); }); - it('should throw an error if id is missing in fromDict', () => { + 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' }, }; - - expect(() => IterableEmbeddedMessageElementsButton.fromDict(dict)).toThrow( + // @ts-expect-error - id is purposely missing + expect(() => new IterableEmbeddedMessageElementsButton(dict)).toThrow( 'id is required' ); }); @@ -78,13 +75,11 @@ describe('IterableEmbeddedMessageButton', () => { action: { type: 'close' }, }; - const button = IterableEmbeddedMessageElementsButton.fromDict(dict); + const button = new IterableEmbeddedMessageElementsButton(dict); expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); expect(button.id).toBe('button-123'); - expect(button.action).toBeInstanceOf( - IterableEmbeddedMessageElementsButtonAction - ); + expect(button.action).toBeInstanceOf(Object); expect(button.action?.type).toBe('close'); expect(button.action?.data).toBeUndefined(); }); diff --git a/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts b/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts deleted file mode 100644 index fa172cb7c..000000000 --- a/src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IterableEmbeddedMessageElementsButtonAction } from '../embedded/classes/IterableEmbeddedMessageElementsButtonAction'; -import { Iterable } from '../core/classes/Iterable'; - -describe('IterableEmbeddedMessageDefaultAction', () => { - it('should create an instance with the correct properties', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElementsButtonAction_fromDict_valid_dictionary' - ); - - const dict = { type: 'openUrl', data: 'https://example.com' }; - const action = IterableEmbeddedMessageElementsButtonAction.fromDict(dict); - expect(action).toBeInstanceOf(IterableEmbeddedMessageElementsButtonAction); - expect(action.type).toBe('openUrl'); - expect(action.data).toBe('https://example.com'); - }); - - it('should create an instance from a dictionary with data omitted', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElementsButtonAction_fromDict_valid_dictionary_with_data_omitted' - ); - - const dict = { type: 'action://join', data: '' }; - const action = IterableEmbeddedMessageElementsButtonAction.fromDict(dict); - expect(action).toBeInstanceOf(IterableEmbeddedMessageElementsButtonAction); - expect(action.type).toBe('action://join'); - expect(action.data).toBe(''); - }); - - it('should throw an error if type is missing in fromDict', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElementsButtonAction_fromDict_invalid_dictionary_missing_type' - ); - - const dict = { data: 'foo' }; - - expect(() => - IterableEmbeddedMessageElementsButtonAction.fromDict(dict) - ).toThrow('type is required'); - }); -}); diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index 73280b7e4..04e2b3b38 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -67,8 +67,8 @@ export class IterableEmbeddedMessageElements { ? IterableEmbeddedMessageDefaultAction.fromDict(dict.defaultAction) : undefined; - const buttons = dict.buttons?.map((button) => - IterableEmbeddedMessageElementsButton.fromDict(button) + const buttons = dict.buttons?.map( + (button) => new IterableEmbeddedMessageElementsButton(button) ); const text = dict.text?.map((text) => diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts index 64a516a20..7d41b50d8 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts @@ -1,5 +1,3 @@ -import { IterableEmbeddedMessageElementsButtonAction } from './IterableEmbeddedMessageElementsButtonAction'; - /** * IterableEmbeddedMessageElementsButton represents a button in an embedded message. */ @@ -9,45 +7,30 @@ export class IterableEmbeddedMessageElementsButton { /** The title for the embedded message button */ readonly title?: string; /** The action for the embedded message button */ - readonly action?: IterableEmbeddedMessageElementsButtonAction; + readonly action?: { + type: string; + data?: string; + }; /** * Creates an instance of IterableEmbeddedMessageButton. * - * @param id - The ID for the embedded message button. - * @param title - The title for the embedded message button. - * @param action - The action for the embedded message button. - */ - constructor( - id: string, - title?: string, - action?: IterableEmbeddedMessageElementsButtonAction - ) { - this.id = id; - this.title = title; - this.action = action; - } - - /** - * Creates an instance of `IterableEmbeddedMessageButton` from a dictionary object. - * * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageButton` instance. - * @returns A new instance of `IterableEmbeddedMessageButton` initialized with the provided dictionary properties. */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageElementsButton { + constructor(dict: EmbeddedMessageElementsButtonDict) { if (!dict.id) { throw new Error('id is required'); } - const action = dict.action - ? IterableEmbeddedMessageElementsButtonAction.fromDict(dict.action) - : undefined; - return new IterableEmbeddedMessageElementsButton( - dict.id, - dict.title, - action - ); + + this.id = dict.id; + this.title = dict.title; + + if (dict.action) { + this.action = { + type: dict.action.type, + data: dict.action.data, + }; + } } } @@ -60,5 +43,8 @@ export interface EmbeddedMessageElementsButtonDict { /** The title for the embedded message button */ title?: string; /** The action for the embedded message button */ - action?: IterableEmbeddedMessageElementsButtonAction; + action?: { + type: string; + data?: string; + }; } diff --git a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts deleted file mode 100644 index 4061e2c6a..000000000 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * IterableEmbeddedMessageElementsButtonAction represents an action defined as - * a response to user events for an embedded message button - */ -export class IterableEmbeddedMessageElementsButtonAction { - /** - * The type of iterable action - * For custom actions, the type is `action://` prefix followed by a custom action name - */ - readonly type: string; - - /** - * The url for the action when the type is `openUrl` - * For custom actions, data is empty - */ - readonly data?: string; - - /** - * Creates an instance of IterableEmbeddedMessageElementsButtonAction. - * - * @param type - The type of the action. - * @param data - Optional data associated with the action. - */ - constructor(type: string, data?: string) { - this.type = type; - this.data = data; - } - - /** - * Creates an instance of `IterableEmbeddedMessageElementsButtonAction` from a dictionary object. - * - * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageElementsButtonAction` instance. - * @returns A new instance of `IterableEmbeddedMessageElementsButtonAction` initialized with the provided dictionary properties. - */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageElementsButtonAction { - if (!dict.type) { - throw new Error('type is required'); - } - return new IterableEmbeddedMessageElementsButtonAction( - dict.type, - dict.data - ); - } -} - -/** - * An interface defining the dictionary object containing the properties for the embedded message button action. - */ -export interface EmbeddedMessageButtonActionDict { - /** The type of the action */ - type: string; - /** The data associated with the action */ - data?: string; -} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts index 3b0ab3fa8..5b943168e 100644 --- a/src/embedded/classes/index.ts +++ b/src/embedded/classes/index.ts @@ -1,5 +1,4 @@ export * from './IterableEmbeddedManager'; export * from './IterableEmbeddedPlacement'; export * from './IterableEmbeddedMessageElementsButton'; -export * from './IterableEmbeddedMessageElementsButtonAction'; export * from './IterableEmbeddedMessageMetadata'; From 5f51526a59da4e256c833916994d6cbddb889ca4 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 15:54:16 -0600 Subject: [PATCH 10/16] feat: updates IterableEmbeddedMessageText class --- .../IterableEmbeddedMessageText.test.ts | 7 ++++--- .../IterableEmbeddedMessageElements.ts | 4 ++-- .../classes/IterableEmbeddedMessageText.ts | 21 +++++-------------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/__tests__/IterableEmbeddedMessageText.test.ts b/src/__tests__/IterableEmbeddedMessageText.test.ts index 10b3e2ffe..2ac677d55 100644 --- a/src/__tests__/IterableEmbeddedMessageText.test.ts +++ b/src/__tests__/IterableEmbeddedMessageText.test.ts @@ -6,7 +6,7 @@ describe('IterableEmbeddedMessageText', () => { Iterable.logger.log('iterableEmbeddedMessageText_fromDict_all_properties'); const dict = { id: 'text-123', text: 'Hello World!', type: 'heading' }; - const text = IterableEmbeddedMessageText.fromDict(dict); + const text = new IterableEmbeddedMessageText(dict); expect(text).toBeInstanceOf(IterableEmbeddedMessageText); expect(text.id).toBe('text-123'); @@ -18,7 +18,7 @@ describe('IterableEmbeddedMessageText', () => { Iterable.logger.log('iterableEmbeddedMessageText_fromDict_required_only'); const dict = { id: 'text-123' }; - const text = IterableEmbeddedMessageText.fromDict(dict); + const text = new IterableEmbeddedMessageText(dict); expect(text).toBeInstanceOf(IterableEmbeddedMessageText); expect(text.id).toBe('text-123'); @@ -31,7 +31,8 @@ describe('IterableEmbeddedMessageText', () => { const dict = { text: 'Hello World!', type: 'heading' }; - expect(() => IterableEmbeddedMessageText.fromDict(dict)).toThrow( + // @ts-expect-error - id is purposely missing + expect(() => new IterableEmbeddedMessageText(dict)).toThrow( 'id is required' ); }); diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index 04e2b3b38..740c0b143 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -71,8 +71,8 @@ export class IterableEmbeddedMessageElements { (button) => new IterableEmbeddedMessageElementsButton(button) ); - const text = dict.text?.map((text) => - IterableEmbeddedMessageText.fromDict(text) + const text = dict.text?.map( + (text) => new IterableEmbeddedMessageText(text) ); return new IterableEmbeddedMessageElements( diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts index 06a553476..3b88dc8a8 100644 --- a/src/embedded/classes/IterableEmbeddedMessageText.ts +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -16,25 +16,14 @@ export class IterableEmbeddedMessageText { * @param text - The text of the text element * @param type - The type of the text element */ - constructor(id: string, text?: string, type?: string) { - this.id = id; - this.text = text; - this.type = type; - } - - /** - * Creates an instance of `IterableEmbeddedMessageText` from a dictionary object. - * - * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageText` instance. - * @returns A new instance of `IterableEmbeddedMessageText` initialized with the provided dictionary properties. - */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageText { + constructor(dict: EmbeddedMessageTextDict) { if (!dict.id) { throw new Error('id is required'); } - return new IterableEmbeddedMessageText(dict.id, dict.text, dict.type); + + this.id = dict.id; + this.text = dict.text; + this.type = dict.type; } } From 7c2c8aa91ad515ea72324c9284a48c52b3452d91 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 16:15:44 -0600 Subject: [PATCH 11/16] feat: converts IterableEmbeddedMessageElements constructor --- .../IterableEmbeddedMessageElements.test.ts | 12 ++-- .../classes/IterableEmbeddedMessage.ts | 2 +- .../IterableEmbeddedMessageElements.ts | 67 ++++--------------- 3 files changed, 20 insertions(+), 61 deletions(-) diff --git a/src/__tests__/IterableEmbeddedMessageElements.test.ts b/src/__tests__/IterableEmbeddedMessageElements.test.ts index 00028da5f..6887c44b6 100644 --- a/src/__tests__/IterableEmbeddedMessageElements.test.ts +++ b/src/__tests__/IterableEmbeddedMessageElements.test.ts @@ -50,7 +50,7 @@ describe('IterableEmbeddedMessageElements', () => { ], }; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Awesome Title'); @@ -108,7 +108,7 @@ describe('IterableEmbeddedMessageElements', () => { body: 'Simple Body', }; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Simple Title'); @@ -127,7 +127,7 @@ describe('IterableEmbeddedMessageElements', () => { const dict = {}; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBeUndefined(); @@ -151,7 +151,7 @@ describe('IterableEmbeddedMessageElements', () => { mediaUrlCaption: 'Check this out!', }; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Media Title'); @@ -177,7 +177,7 @@ describe('IterableEmbeddedMessageElements', () => { }, }; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Action Title'); @@ -203,7 +203,7 @@ describe('IterableEmbeddedMessageElements', () => { text: [], }; - const elements = IterableEmbeddedMessageElements.fromDict(dict); + const elements = new IterableEmbeddedMessageElements(dict); expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Empty Arrays Title'); diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index c264bf262..1ba7d330f 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -41,7 +41,7 @@ export class IterableEmbeddedMessage { } const metadata = IterableEmbeddedMessageMetadata.fromDict(dict.metadata); const elements = dict.elements - ? IterableEmbeddedMessageElements.fromDict(dict.elements) + ? new IterableEmbeddedMessageElements(dict.elements) : undefined; const payload = dict.payload; return new IterableEmbeddedMessage(metadata, elements, payload); diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index 740c0b143..d684a6816 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -2,6 +2,9 @@ import { IterableEmbeddedMessageDefaultAction } from './IterableEmbeddedMessageD import { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMessageElementsButton'; import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; +import type { EmbeddedMessageElementsButtonDict } from './IterableEmbeddedMessageElementsButton'; +import type { EmbeddedMessageTextDict } from './IterableEmbeddedMessageText'; + /** * IterableEmbeddedMessageElements represents the elements of an embedded message. */ @@ -24,66 +27,22 @@ export class IterableEmbeddedMessageElements { /** * Creates an instance of `IterableEmbeddedMessageElements`. * - * @param title - The title of the embedded message. - * @param body - The body of the embedded message. - * @param mediaUrl - The url of the embedded message image. - * @param mediaUrlCaption - The caption of the embedded message image. - * @param defaultAction - The default action of the embedded message. - * @param buttons - The buttons of the embedded message. - * @param text - The text elements of the embedded message. - */ - constructor( - title?: string, - body?: string, - mediaUrl?: string, - mediaUrlCaption?: string, - defaultAction?: IterableEmbeddedMessageDefaultAction, - buttons?: IterableEmbeddedMessageElementsButton[], - text?: IterableEmbeddedMessageText[] - ) { - this.title = title; - this.body = body; - this.mediaUrl = mediaUrl; - this.mediaUrlCaption = mediaUrlCaption; - this.defaultAction = defaultAction; - this.buttons = buttons; - this.text = text; - } - - /** - * Creates an instance of `IterableEmbeddedMessageElements` from a dictionary object. - * * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageElements` instance. - * @returns A new instance of `IterableEmbeddedMessageElements` initialized with the provided dictionary properties. */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageElements { - const title = dict.title; - const body = dict.body; - const mediaUrl = dict.mediaUrl; - const mediaUrlCaption = dict.mediaUrlCaption; - const defaultAction = dict.defaultAction + constructor(dict: Partial) { + this.title = dict.title; + this.body = dict.body; + this.mediaUrl = dict.mediaUrl; + this.mediaUrlCaption = dict.mediaUrlCaption; + this.defaultAction = dict.defaultAction ? IterableEmbeddedMessageDefaultAction.fromDict(dict.defaultAction) : undefined; - const buttons = dict.buttons?.map( + this.buttons = dict.buttons?.map( (button) => new IterableEmbeddedMessageElementsButton(button) ); - const text = dict.text?.map( - (text) => new IterableEmbeddedMessageText(text) - ); - - return new IterableEmbeddedMessageElements( - title, - body, - mediaUrl, - mediaUrlCaption, - defaultAction, - buttons, - text - ); + this.text = dict.text?.map((text) => new IterableEmbeddedMessageText(text)); } } @@ -102,7 +61,7 @@ export interface EmbeddedMessageElementsDict { /** The default action of the embedded message */ defaultAction?: IterableEmbeddedMessageDefaultAction; /** The buttons of the embedded message */ - buttons?: IterableEmbeddedMessageElementsButton[]; + buttons?: EmbeddedMessageElementsButtonDict[]; /** The text elements of the embedded message */ - text?: IterableEmbeddedMessageText[]; + text?: EmbeddedMessageTextDict[]; } From 3c0e01dea27117d2f82c8d311ea2e6a71ac1d168 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 16:23:54 -0600 Subject: [PATCH 12/16] feat: removes default action class --- ...erableEmbeddedMessageDefaultAction.test.ts | 40 -------------- .../IterableEmbeddedMessageElements.test.ts | 9 +--- .../IterableEmbeddedMessageDefaultAction.ts | 53 ------------------- .../IterableEmbeddedMessageElements.ts | 21 +++++--- 4 files changed, 17 insertions(+), 106 deletions(-) delete mode 100644 src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts delete mode 100644 src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts diff --git a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts deleted file mode 100644 index bf99bc5b8..000000000 --- a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IterableEmbeddedMessageDefaultAction } from '../embedded/classes/IterableEmbeddedMessageDefaultAction'; -import { Iterable } from '../core/classes/Iterable'; - -describe('IterableEmbeddedMessageDefaultAction', () => { - it('should create an instance with the correct properties', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageDefaultAction_fromDict_valid_dictionary' - ); - - const dict = { type: 'openUrl', data: 'https://example.com' }; - const action = IterableEmbeddedMessageDefaultAction.fromDict(dict); - expect(action).toBeInstanceOf(IterableEmbeddedMessageDefaultAction); - expect(action.type).toBe('openUrl'); - expect(action.data).toBe('https://example.com'); - }); - - it('should create an instance from a dictionary with data omitted', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageDefaultAction_fromDict_valid_dictionary_with_data_omitted' - ); - - const dict = { type: 'action://join', data: '' }; - const action = IterableEmbeddedMessageDefaultAction.fromDict(dict); - expect(action).toBeInstanceOf(IterableEmbeddedMessageDefaultAction); - expect(action.type).toBe('action://join'); - expect(action.data).toBe(''); - }); - - it('should throw an error if type is missing in fromDict', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageDefaultAction_fromDict_invalid_dictionary_missing_type' - ); - - const dict = { data: 'foo' }; - - expect(() => IterableEmbeddedMessageDefaultAction.fromDict(dict)).toThrow( - 'type is required' - ); - }); -}); diff --git a/src/__tests__/IterableEmbeddedMessageElements.test.ts b/src/__tests__/IterableEmbeddedMessageElements.test.ts index 6887c44b6..64df9021a 100644 --- a/src/__tests__/IterableEmbeddedMessageElements.test.ts +++ b/src/__tests__/IterableEmbeddedMessageElements.test.ts @@ -1,5 +1,4 @@ import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; -import { IterableEmbeddedMessageDefaultAction } from '../embedded/classes/IterableEmbeddedMessageDefaultAction'; import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; import { IterableEmbeddedMessageText } from '../embedded/classes/IterableEmbeddedMessageText'; import { Iterable } from '../core/classes/Iterable'; @@ -59,9 +58,7 @@ describe('IterableEmbeddedMessageElements', () => { expect(elements.mediaUrlCaption).toBe('Check out this sick image!'); // Check defaultAction - expect(elements.defaultAction).toBeInstanceOf( - IterableEmbeddedMessageDefaultAction - ); + expect(elements.defaultAction).toBeInstanceOf(Object); expect(elements.defaultAction?.type).toBe('openUrl'); expect(elements.defaultAction?.data).toBe('https://example.com'); @@ -182,9 +179,7 @@ describe('IterableEmbeddedMessageElements', () => { expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(elements.title).toBe('Action Title'); expect(elements.body).toBe('Action Body'); - expect(elements.defaultAction).toBeInstanceOf( - IterableEmbeddedMessageDefaultAction - ); + expect(elements.defaultAction).toBeInstanceOf(Object); expect(elements.defaultAction?.type).toBe('openUrl'); expect(elements.defaultAction?.data).toBe('https://example.com'); expect(elements.buttons).toBeUndefined(); diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts deleted file mode 100644 index 06a36c00e..000000000 --- a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * IterableEmbeddedMessageDefaultAction represents the default action defined as - * a response to user events for an embedded message - */ -export class IterableEmbeddedMessageDefaultAction { - /** - * The type of iterable action - * For custom actions, the type is `action://` prefix followed by a custom action name - */ - readonly type: string; - - /** - * The url for the action when the type is `openUrl` - * For custom actions, data is empty - */ - readonly data?: string; - - /** - * Creates an instance of `IterableEmbeddedMessageDefaultAction`. - * - * @param type - The type of iterable action - * @param data - The url for the action when the type is `openUrl` - */ - constructor(type: string, data?: string) { - this.type = type; - this.data = data; - } - - /** - * Creates an instance of `IterableEmbeddedMessageDefaultAction` from a dictionary object. - * - * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageDefaultAction` instance. - * @returns A new instance of `IterableEmbeddedMessageDefaultAction` initialized with the provided dictionary properties. - */ - static fromDict( - dict: Partial - ): IterableEmbeddedMessageDefaultAction { - if (!dict.type) { - throw new Error('type is required'); - } - return new IterableEmbeddedMessageDefaultAction(dict.type, dict.data); - } -} - -/** - * An interface defining the dictionary object containing the properties for the embedded message default action. - */ -export interface EmbeddedMessageDefaultActionDict { - /** The type of the action */ - type: string; - /** The url for the action when the type is `openUrl` */ - data?: string; -} diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index d684a6816..b5dc63b6a 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -1,4 +1,3 @@ -import { IterableEmbeddedMessageDefaultAction } from './IterableEmbeddedMessageDefaultAction'; import { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMessageElementsButton'; import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; @@ -18,7 +17,10 @@ export class IterableEmbeddedMessageElements { /** The caption of the embedded message image */ readonly mediaUrlCaption?: string; /** The default action of the embedded message */ - readonly defaultAction?: IterableEmbeddedMessageDefaultAction; + readonly defaultAction?: { + type: string; + data?: string; + }; /** The buttons of the embedded message */ readonly buttons?: IterableEmbeddedMessageElementsButton[]; /** The text elements of the embedded message */ @@ -34,9 +36,13 @@ export class IterableEmbeddedMessageElements { this.body = dict.body; this.mediaUrl = dict.mediaUrl; this.mediaUrlCaption = dict.mediaUrlCaption; - this.defaultAction = dict.defaultAction - ? IterableEmbeddedMessageDefaultAction.fromDict(dict.defaultAction) - : undefined; + + if (dict.defaultAction) { + this.defaultAction = { + type: dict.defaultAction.type, + data: dict.defaultAction.data, + }; + } this.buttons = dict.buttons?.map( (button) => new IterableEmbeddedMessageElementsButton(button) @@ -59,7 +65,10 @@ export interface EmbeddedMessageElementsDict { /** The caption of the embedded message image */ mediaUrlCaption?: string; /** The default action of the embedded message */ - defaultAction?: IterableEmbeddedMessageDefaultAction; + defaultAction?: { + type: string; + data?: string; + }; /** The buttons of the embedded message */ buttons?: EmbeddedMessageElementsButtonDict[]; /** The text elements of the embedded message */ From 56b33b02304cfca8858a1e02912dfab7679bdda1 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 16:45:32 -0600 Subject: [PATCH 13/16] feat: converts IterableEmbeddedMessage constructor --- src/__tests__/IterableEmbeddedMessage.test.ts | 13 +++-- .../IterableEmbeddedMessageMetadata.test.ts | 7 ++- .../classes/IterableEmbeddedMessage.ts | 58 +++++++++---------- .../IterableEmbeddedMessageMetadata.ts | 39 ++----------- 4 files changed, 44 insertions(+), 73 deletions(-) diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts index 64385e99f..79c6d660b 100644 --- a/src/__tests__/IterableEmbeddedMessage.test.ts +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -47,12 +47,12 @@ describe('IterableEmbeddedMessage', () => { }, }; - const message = IterableEmbeddedMessage.fromDict(dict); + const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); // Check metadata - expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata).toBeInstanceOf(Object); expect(message.metadata.messageId).toBe('msg-123'); expect(message.metadata.placementId).toBe(1); expect(message.metadata.campaignId).toBe(456); @@ -85,7 +85,7 @@ describe('IterableEmbeddedMessage', () => { }, }; - const message = IterableEmbeddedMessage.fromDict(dict); + const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); @@ -107,7 +107,8 @@ describe('IterableEmbeddedMessage', () => { }, }; - expect(() => IterableEmbeddedMessage.fromDict(dict)).toThrow( + // @ts-expect-error - metadata is purposely missing + expect(() => new IterableEmbeddedMessage(dict)).toThrow( 'metadata is required' ); }); @@ -127,7 +128,7 @@ describe('IterableEmbeddedMessage', () => { }, }; - const message = IterableEmbeddedMessage.fromDict(dict); + const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); @@ -151,7 +152,7 @@ describe('IterableEmbeddedMessage', () => { }, }; - const message = IterableEmbeddedMessage.fromDict(dict); + const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); diff --git a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts index f57786a9d..8f03a5287 100644 --- a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts +++ b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts @@ -14,7 +14,7 @@ describe('IterableEmbeddedMessage', () => { isProof: false, }; - const result = IterableEmbeddedMessageMetadata.fromDict(dict); + const result = new IterableEmbeddedMessageMetadata(dict); expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); expect(result.messageId).toBe('123'); @@ -33,7 +33,7 @@ describe('IterableEmbeddedMessage', () => { placementId: 456, }; - const result = IterableEmbeddedMessageMetadata.fromDict(dict); + const result = new IterableEmbeddedMessageMetadata(dict); expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); expect(result.messageId).toBe('123'); @@ -52,7 +52,8 @@ describe('IterableEmbeddedMessage', () => { }; expect(() => { - IterableEmbeddedMessageMetadata.fromDict(dict); + // @ts-expect-error - messageId is purposely missing + new IterableEmbeddedMessageMetadata(dict); }).toThrow('messageId and placementId are required'); }); }); diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index 1ba7d330f..c3fbe9607 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -1,61 +1,57 @@ -import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; +import type { EmbeddedMessageElementsDict } from './IterableEmbeddedMessageElements'; /** * IterableEmbeddedMessage represents an embedded message. */ export class IterableEmbeddedMessage { /** The metadata of the embedded message */ - metadata: IterableEmbeddedMessageMetadata; + readonly metadata: { + messageId: string; + placementId: number; + campaignId?: number; + isProof: boolean; + }; /** The elements of the embedded message */ - elements?: IterableEmbeddedMessageElements; + readonly elements?: IterableEmbeddedMessageElements; /** The custom payload of the embedded message */ - payload?: Record; + readonly payload?: Record; /** * Creates an instance of `IterableEmbeddedMessage`. * - * @param metadata - The metadata of the embedded message. - * @param elements - The elements of the embedded message. - * @param payload - The custom payload of the embedded message. - */ - constructor( - metadata: IterableEmbeddedMessageMetadata, - elements?: IterableEmbeddedMessageElements, - payload?: Record - ) { - this.metadata = metadata; - this.elements = elements; - this.payload = payload; - } - - /** - * Creates an instance of `IterableEmbeddedMessage` from a dictionary object. - * * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessage` instance. - * @returns A new instance of `IterableEmbeddedMessage` initialized with the provided dictionary properties. */ - static fromDict(dict: Partial): IterableEmbeddedMessage { + constructor(dict: EmbeddedMessageDict) { if (!dict.metadata) { throw new Error('metadata is required'); } - const metadata = IterableEmbeddedMessageMetadata.fromDict(dict.metadata); - const elements = dict.elements + this.metadata = { + messageId: dict.metadata.messageId, + placementId: dict.metadata.placementId, + campaignId: dict.metadata.campaignId, + isProof: dict.metadata.isProof, + }; + this.elements = dict.elements ? new IterableEmbeddedMessageElements(dict.elements) : undefined; - const payload = dict.payload; - return new IterableEmbeddedMessage(metadata, elements, payload); + this.payload = dict.payload; } } /** * An interface defining the dictionary object containing the properties for the embedded message. */ -interface EmbeddedMessageDict { +export interface EmbeddedMessageDict { /** The metadata of the embedded message */ - metadata: IterableEmbeddedMessageMetadata; + metadata: { + messageId: string; + placementId: number; + campaignId?: number; + isProof: boolean; + }; /** The elements of the embedded message */ - elements: IterableEmbeddedMessageElements; + elements?: EmbeddedMessageElementsDict; /** The custom payload of the embedded message */ - payload: Record; + payload?: Record; } diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts index 02bc032d0..8be0cfa5c 100644 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -14,43 +14,16 @@ export class IterableEmbeddedMessageMetadata { /** * 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. + * @param dict - The dictionary object containing the metadata properties. */ - 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 { + constructor(dict: EmbeddedMessageMetadataDict) { if (!dict.messageId || !dict.placementId) { throw new Error('messageId and placementId are required'); } - return new IterableEmbeddedMessageMetadata( - dict.messageId, - dict.placementId, - dict.campaignId, - dict.isProof - ); + this.messageId = dict.messageId; + this.placementId = dict.placementId; + this.campaignId = dict.campaignId; + this.isProof = dict.isProof ?? false; } } From 1db3333ab74d5cd930cd96fa43ad8ece5eaad3b2 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 16:48:02 -0600 Subject: [PATCH 14/16] feat: converts IterableEmbeddedMessage constructor --- src/__tests__/IterableEmbeddedMessage.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts index 79c6d660b..fdcb145ef 100644 --- a/src/__tests__/IterableEmbeddedMessage.test.ts +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -1,5 +1,4 @@ import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; -import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; import { Iterable } from '../core/classes/Iterable'; @@ -88,7 +87,7 @@ describe('IterableEmbeddedMessage', () => { const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); - expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata).toBeInstanceOf(Object); expect(message.metadata.messageId).toBe('msg-123'); expect(message.metadata.placementId).toBe(1); expect(message.metadata.campaignId).toBeUndefined(); @@ -131,7 +130,7 @@ describe('IterableEmbeddedMessage', () => { const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); - expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata).toBeInstanceOf(Object); expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); expect(message.elements?.title).toBe('Elements Only'); expect(message.elements?.body).toBe('No payload here'); @@ -155,7 +154,7 @@ describe('IterableEmbeddedMessage', () => { const message = new IterableEmbeddedMessage(dict); expect(message).toBeInstanceOf(IterableEmbeddedMessage); - expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata).toBeInstanceOf(Object); expect(message.elements).toBeUndefined(); expect(message.payload).toEqual({ someData: 'someValue', From 2b627f30d06b17f7ae383490efbc1ee5e0064dc6 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Thu, 26 Jun 2025 16:50:48 -0600 Subject: [PATCH 15/16] feat: removes embedded metadata class --- .../IterableEmbeddedMessageMetadata.test.ts | 59 ------------------- .../IterableEmbeddedMessageMetadata.ts | 38 ------------ src/embedded/classes/index.ts | 4 +- 3 files changed, 3 insertions(+), 98 deletions(-) delete mode 100644 src/__tests__/IterableEmbeddedMessageMetadata.test.ts delete mode 100644 src/embedded/classes/IterableEmbeddedMessageMetadata.ts diff --git a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts b/src/__tests__/IterableEmbeddedMessageMetadata.test.ts deleted file mode 100644 index 8f03a5287..000000000 --- a/src/__tests__/IterableEmbeddedMessageMetadata.test.ts +++ /dev/null @@ -1,59 +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 = new IterableEmbeddedMessageMetadata(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 = new IterableEmbeddedMessageMetadata(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(() => { - // @ts-expect-error - messageId is purposely missing - new IterableEmbeddedMessageMetadata(dict); - }).toThrow('messageId and placementId are required'); - }); -}); diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts deleted file mode 100644 index 8be0cfa5c..000000000 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ /dev/null @@ -1,38 +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 dict - The dictionary object containing the metadata properties. - */ - constructor(dict: EmbeddedMessageMetadataDict) { - if (!dict.messageId || !dict.placementId) { - throw new Error('messageId and placementId are required'); - } - this.messageId = dict.messageId; - this.placementId = dict.placementId; - this.campaignId = dict.campaignId; - this.isProof = dict.isProof ?? false; - } -} - -/** - * 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/index.ts b/src/embedded/classes/index.ts index 5b943168e..578dc28be 100644 --- a/src/embedded/classes/index.ts +++ b/src/embedded/classes/index.ts @@ -1,4 +1,6 @@ export * from './IterableEmbeddedManager'; export * from './IterableEmbeddedPlacement'; +export * from './IterableEmbeddedMessage'; +export * from './IterableEmbeddedMessageElements'; export * from './IterableEmbeddedMessageElementsButton'; -export * from './IterableEmbeddedMessageMetadata'; +export * from './IterableEmbeddedMessageText'; From 6701a64498d7b47a85f8f046fa1ac6bec3d6802f Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Fri, 27 Jun 2025 10:17:01 -0600 Subject: [PATCH 16/16] feat: combines message and elements in one class --- src/__tests__/IterableEmbeddedMessage.test.ts | 5 +- ... => IterableEmbeddedMessageButton.test.ts} | 20 +- .../IterableEmbeddedMessageElements.test.ts | 209 ------------------ .../classes/IterableEmbeddedMessage.ts | 88 +++++++- ...on.ts => IterableEmbeddedMessageButton.ts} | 6 +- .../IterableEmbeddedMessageElements.ts | 76 ------- src/embedded/classes/index.ts | 3 +- 7 files changed, 99 insertions(+), 308 deletions(-) rename src/__tests__/{IterableEmbeddedMessageElementsButton.test.ts => IterableEmbeddedMessageButton.test.ts} (73%) delete mode 100644 src/__tests__/IterableEmbeddedMessageElements.test.ts rename src/embedded/classes/{IterableEmbeddedMessageElementsButton.ts => IterableEmbeddedMessageButton.ts} (84%) delete mode 100644 src/embedded/classes/IterableEmbeddedMessageElements.ts diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts index fdcb145ef..3e8a99b7c 100644 --- a/src/__tests__/IterableEmbeddedMessage.test.ts +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -1,5 +1,4 @@ import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; -import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; import { Iterable } from '../core/classes/Iterable'; describe('IterableEmbeddedMessage', () => { @@ -58,7 +57,7 @@ describe('IterableEmbeddedMessage', () => { expect(message.metadata.isProof).toBe(false); // Check elements - expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); + 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'); @@ -131,7 +130,7 @@ describe('IterableEmbeddedMessage', () => { expect(message).toBeInstanceOf(IterableEmbeddedMessage); expect(message.metadata).toBeInstanceOf(Object); - expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(message.elements).toBeInstanceOf(Object); expect(message.elements?.title).toBe('Elements Only'); expect(message.elements?.body).toBe('No payload here'); expect(message.payload).toBeUndefined(); diff --git a/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts b/src/__tests__/IterableEmbeddedMessageButton.test.ts similarity index 73% rename from src/__tests__/IterableEmbeddedMessageElementsButton.test.ts rename to src/__tests__/IterableEmbeddedMessageButton.test.ts index b9dcf1d7b..5e20f90c0 100644 --- a/src/__tests__/IterableEmbeddedMessageElementsButton.test.ts +++ b/src/__tests__/IterableEmbeddedMessageButton.test.ts @@ -1,4 +1,4 @@ -import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; +import { IterableEmbeddedMessageButton } from '../embedded/classes/IterableEmbeddedMessageButton'; import { Iterable } from '../core/classes/Iterable'; describe('IterableEmbeddedMessageButton', () => { @@ -13,9 +13,9 @@ describe('IterableEmbeddedMessageButton', () => { action: { type: 'openUrl', data: 'https://example.com' }, }; - const button = new IterableEmbeddedMessageElementsButton(dict); + const button = new IterableEmbeddedMessageButton(dict); - expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); expect(button.id).toBe('button-123'); expect(button.title).toBe('Click Me!'); expect(button.action).toBeInstanceOf(Object); @@ -28,9 +28,9 @@ describe('IterableEmbeddedMessageButton', () => { const dict = { id: 'button-123' }; - const button = new IterableEmbeddedMessageElementsButton(dict); + const button = new IterableEmbeddedMessageButton(dict); - expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); expect(button.id).toBe('button-123'); expect(button.title).toBeUndefined(); expect(button.action).toBeUndefined(); @@ -44,9 +44,9 @@ describe('IterableEmbeddedMessageButton', () => { title: 'Click Me!', }; - const button = new IterableEmbeddedMessageElementsButton(dict); + const button = new IterableEmbeddedMessageButton(dict); - expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); expect(button.id).toBe('button-123'); expect(button.title).toBe('Click Me!'); expect(button.action).toBeUndefined(); @@ -60,7 +60,7 @@ describe('IterableEmbeddedMessageButton', () => { action: { type: 'openUrl', data: 'https://example.com' }, }; // @ts-expect-error - id is purposely missing - expect(() => new IterableEmbeddedMessageElementsButton(dict)).toThrow( + expect(() => new IterableEmbeddedMessageButton(dict)).toThrow( 'id is required' ); }); @@ -75,9 +75,9 @@ describe('IterableEmbeddedMessageButton', () => { action: { type: 'close' }, }; - const button = new IterableEmbeddedMessageElementsButton(dict); + const button = new IterableEmbeddedMessageButton(dict); - expect(button).toBeInstanceOf(IterableEmbeddedMessageElementsButton); + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); expect(button.id).toBe('button-123'); expect(button.action).toBeInstanceOf(Object); expect(button.action?.type).toBe('close'); diff --git a/src/__tests__/IterableEmbeddedMessageElements.test.ts b/src/__tests__/IterableEmbeddedMessageElements.test.ts deleted file mode 100644 index 64df9021a..000000000 --- a/src/__tests__/IterableEmbeddedMessageElements.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; -import { IterableEmbeddedMessageElementsButton } from '../embedded/classes/IterableEmbeddedMessageElementsButton'; -import { IterableEmbeddedMessageText } from '../embedded/classes/IterableEmbeddedMessageText'; -import { Iterable } from '../core/classes/Iterable'; - -describe('IterableEmbeddedMessageElements', () => { - it('should create an instance with all properties', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_all_properties' - ); - - const dict = { - 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', - }, - }, - { - id: 'button-2', - title: 'Close', - action: { - type: 'action://dismiss', - }, - }, - ], - text: [ - { - id: 'text-1', - text: 'Some cool text', - type: 'body', - }, - { - id: 'text-2', - text: 'More radical text', - type: 'subtitle', - }, - ], - }; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBe('Awesome Title'); - expect(elements.body).toBe('Radical Body Text'); - expect(elements.mediaUrl).toBe('https://example.com/image.jpg'); - expect(elements.mediaUrlCaption).toBe('Check out this sick image!'); - - // Check defaultAction - expect(elements.defaultAction).toBeInstanceOf(Object); - expect(elements.defaultAction?.type).toBe('openUrl'); - expect(elements.defaultAction?.data).toBe('https://example.com'); - - // Check buttons - expect(elements.buttons).toHaveLength(2); - const firstButton = elements - .buttons![0] as IterableEmbeddedMessageElementsButton; - expect(firstButton).toBeInstanceOf(IterableEmbeddedMessageElementsButton); - expect(firstButton.id).toBe('button-1'); - expect(firstButton.title).toBe('Click Me!'); - expect(firstButton.action?.type).toBe('openUrl'); - expect(firstButton.action?.data).toBe('https://example.com/button1'); - - const secondButton = elements - .buttons![1] as IterableEmbeddedMessageElementsButton; - expect(secondButton).toBeInstanceOf(IterableEmbeddedMessageElementsButton); - expect(secondButton.id).toBe('button-2'); - expect(secondButton.title).toBe('Close'); - expect(secondButton.action?.type).toBe('action://dismiss'); - expect(secondButton.action?.data).toBeUndefined(); - - // Check text elements - expect(elements.text).toHaveLength(2); - const firstText = elements.text![0] as IterableEmbeddedMessageText; - expect(firstText).toBeInstanceOf(IterableEmbeddedMessageText); - expect(firstText.id).toBe('text-1'); - expect(firstText.text).toBe('Some cool text'); - expect(firstText.type).toBe('body'); - - const secondText = elements.text![1] as IterableEmbeddedMessageText; - expect(secondText).toBeInstanceOf(IterableEmbeddedMessageText); - expect(secondText.id).toBe('text-2'); - expect(secondText.text).toBe('More radical text'); - expect(secondText.type).toBe('subtitle'); - }); - - it('should create an instance with title and body', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_title_and_body' - ); - - const dict = { - title: 'Simple Title', - body: 'Simple Body', - }; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBe('Simple Title'); - expect(elements.body).toBe('Simple Body'); - expect(elements.mediaUrl).toBeUndefined(); - expect(elements.mediaUrlCaption).toBeUndefined(); - expect(elements.defaultAction).toBeUndefined(); - expect(elements.buttons).toBeUndefined(); - expect(elements.text).toBeUndefined(); - }); - - it('should create an instance with no title or body', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_no_title_or_body' - ); - - const dict = {}; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBeUndefined(); - expect(elements.body).toBeUndefined(); - expect(elements.mediaUrl).toBeUndefined(); - expect(elements.mediaUrlCaption).toBeUndefined(); - expect(elements.defaultAction).toBeUndefined(); - expect(elements.buttons).toBeUndefined(); - expect(elements.text).toBeUndefined(); - }); - - it('should create an instance with media properties', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_media_properties' - ); - - const dict = { - title: 'Media Title', - body: 'Media Body', - mediaUrl: 'https://example.com/media.jpg', - mediaUrlCaption: 'Check this out!', - }; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBe('Media Title'); - expect(elements.body).toBe('Media Body'); - expect(elements.mediaUrl).toBe('https://example.com/media.jpg'); - expect(elements.mediaUrlCaption).toBe('Check this out!'); - expect(elements.defaultAction).toBeUndefined(); - expect(elements.buttons).toBeUndefined(); - expect(elements.text).toBeUndefined(); - }); - - it('should create an instance with defaultAction only', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_defaultAction_only' - ); - - const dict = { - title: 'Action Title', - body: 'Action Body', - defaultAction: { - type: 'openUrl', - data: 'https://example.com', - }, - }; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBe('Action Title'); - expect(elements.body).toBe('Action Body'); - expect(elements.defaultAction).toBeInstanceOf(Object); - expect(elements.defaultAction?.type).toBe('openUrl'); - expect(elements.defaultAction?.data).toBe('https://example.com'); - expect(elements.buttons).toBeUndefined(); - expect(elements.text).toBeUndefined(); - }); - - it('should create an instance with empty arrays for buttons and text', () => { - Iterable.logger.log( - 'iterableEmbeddedMessageElements_fromDict_empty_arrays' - ); - - const dict = { - title: 'Empty Arrays Title', - body: 'Empty Arrays Body', - buttons: [], - text: [], - }; - - const elements = new IterableEmbeddedMessageElements(dict); - - expect(elements).toBeInstanceOf(IterableEmbeddedMessageElements); - expect(elements.title).toBe('Empty Arrays Title'); - expect(elements.body).toBe('Empty Arrays Body'); - expect(elements.buttons).toHaveLength(0); - expect(elements.text).toHaveLength(0); - }); -}); diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index c3fbe9607..9d528504d 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -1,5 +1,5 @@ -import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; -import type { EmbeddedMessageElementsDict } from './IterableEmbeddedMessageElements'; +import { IterableEmbeddedMessageButton } from './IterableEmbeddedMessageButton'; +import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; /** * IterableEmbeddedMessage represents an embedded message. @@ -7,13 +7,37 @@ import type { EmbeddedMessageElementsDict } from './IterableEmbeddedMessageEleme 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?: IterableEmbeddedMessageElements; + 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; @@ -32,9 +56,35 @@ export class IterableEmbeddedMessage { campaignId: dict.metadata.campaignId, isProof: dict.metadata.isProof, }; - this.elements = dict.elements - ? new IterableEmbeddedMessageElements(dict.elements) - : undefined; + + 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; } } @@ -45,13 +95,37 @@ export class IterableEmbeddedMessage { 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?: EmbeddedMessageElementsDict; + 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/IterableEmbeddedMessageElementsButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts similarity index 84% rename from src/embedded/classes/IterableEmbeddedMessageElementsButton.ts rename to src/embedded/classes/IterableEmbeddedMessageButton.ts index 7d41b50d8..9a0896d96 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElementsButton.ts +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -1,14 +1,16 @@ /** * IterableEmbeddedMessageElementsButton represents a button in an embedded message. */ -export class IterableEmbeddedMessageElementsButton { +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; }; @@ -44,7 +46,9 @@ export interface EmbeddedMessageElementsButtonDict { 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/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts deleted file mode 100644 index b5dc63b6a..000000000 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { IterableEmbeddedMessageElementsButton } from './IterableEmbeddedMessageElementsButton'; -import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; - -import type { EmbeddedMessageElementsButtonDict } from './IterableEmbeddedMessageElementsButton'; -import type { EmbeddedMessageTextDict } from './IterableEmbeddedMessageText'; - -/** - * IterableEmbeddedMessageElements represents the elements of an embedded message. - */ -export class IterableEmbeddedMessageElements { - /** The title of the embedded message */ - readonly title?: string; - /** The body of the embedded message */ - readonly body?: string; - /** The url of the embedded message image */ - readonly mediaUrl?: string; - /** The caption of the embedded message image */ - readonly mediaUrlCaption?: string; - /** The default action of the embedded message */ - readonly defaultAction?: { - type: string; - data?: string; - }; - /** The buttons of the embedded message */ - readonly buttons?: IterableEmbeddedMessageElementsButton[]; - /** The text elements of the embedded message */ - readonly text?: IterableEmbeddedMessageText[]; - - /** - * Creates an instance of `IterableEmbeddedMessageElements`. - * - * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessageElements` instance. - */ - constructor(dict: Partial) { - this.title = dict.title; - this.body = dict.body; - this.mediaUrl = dict.mediaUrl; - this.mediaUrlCaption = dict.mediaUrlCaption; - - if (dict.defaultAction) { - this.defaultAction = { - type: dict.defaultAction.type, - data: dict.defaultAction.data, - }; - } - - this.buttons = dict.buttons?.map( - (button) => new IterableEmbeddedMessageElementsButton(button) - ); - - this.text = dict.text?.map((text) => new IterableEmbeddedMessageText(text)); - } -} - -/** - * An interface defining the dictionary object containing the properties for the embedded message elements. - */ -export interface EmbeddedMessageElementsDict { - /** The title of the embedded message */ - title?: string; - /** The body of the embedded message */ - body?: string; - /** The url of the embedded message image */ - mediaUrl?: string; - /** The caption of the embedded message image */ - mediaUrlCaption?: string; - /** The default action of the embedded message */ - defaultAction?: { - type: string; - data?: string; - }; - /** The buttons of the embedded message */ - buttons?: EmbeddedMessageElementsButtonDict[]; - /** The text elements of the embedded message */ - text?: EmbeddedMessageTextDict[]; -} diff --git a/src/embedded/classes/index.ts b/src/embedded/classes/index.ts index 578dc28be..2a2abba63 100644 --- a/src/embedded/classes/index.ts +++ b/src/embedded/classes/index.ts @@ -1,6 +1,5 @@ export * from './IterableEmbeddedManager'; export * from './IterableEmbeddedPlacement'; export * from './IterableEmbeddedMessage'; -export * from './IterableEmbeddedMessageElements'; -export * from './IterableEmbeddedMessageElementsButton'; +export * from './IterableEmbeddedMessageButton'; export * from './IterableEmbeddedMessageText';