From d83d2ec6cc63fd22f050b1c8ccd6bdace55180bf Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Fri, 23 May 2025 14:22:43 -0600 Subject: [PATCH 01/18] feat: adds constructor and messages list to IterableEmbeddedPlacement --- src/embedded/classes/IterableEmbeddedPlacement.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/embedded/classes/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts index 6a02aabfe..1173ffad7 100644 --- a/src/embedded/classes/IterableEmbeddedPlacement.ts +++ b/src/embedded/classes/IterableEmbeddedPlacement.ts @@ -1,3 +1,5 @@ +import { IterableEmbeddedMessage } from './IterableEmbeddedMessage'; + /** * Iterable embedded placement * Contains placement id and the associated embedded messages @@ -5,7 +7,10 @@ export class IterableEmbeddedPlacement { readonly placementId: number; - constructor(placementId: number) { + readonly messages: IterableEmbeddedMessage[]; + + constructor(placementId: number, messages: IterableEmbeddedMessage[]) { this.placementId = placementId; + this.messages = messages; } } From 6a63bbdb293c462de12dcf229413f1d1779c072c Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Fri, 23 May 2025 15:00:34 -0600 Subject: [PATCH 02/18] feat: adds constructor to IterableEmbeddedMessage --- .../classes/IterableEmbeddedMessage.ts | 18 ++++++++++++++++++ .../classes/IterableEmbeddedMessageElements.ts | 1 + .../classes/IterableEmbeddedMessageMetadata.ts | 1 + 3 files changed, 20 insertions(+) create mode 100644 src/embedded/classes/IterableEmbeddedMessage.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageElements.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageMetadata.ts diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..41742fa0e --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -0,0 +1,18 @@ +import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; + +export class IterableEmbeddedMessage { + readonly metadata: IterableEmbeddedMessageMetadata; + readonly elements: IterableEmbeddedMessageElements; + readonly payload: Record; + + constructor( + metadata: IterableEmbeddedMessageMetadata, + elements: IterableEmbeddedMessageElements, + payload: Record + ) { + this.metadata = metadata; + this.elements = elements; + this.payload = payload; + } +} diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts new file mode 100644 index 000000000..22eedf6a8 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -0,0 +1 @@ +export class IterableEmbeddedMessageElements {} diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts new file mode 100644 index 000000000..53f0240f0 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -0,0 +1 @@ +export class IterableEmbeddedMessageMetadata {} From b78f8e05be03787c9f1d1c2ac3ade92895974d0b Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Sat, 24 May 2025 10:33:59 -0600 Subject: [PATCH 03/18] feat: adds IterableEmbeddedMessageMetadata class --- .../classes/IterableEmbeddedMessage.ts | 6 ++-- .../IterableEmbeddedMessageMetadata.ts | 34 ++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index 41742fa0e..fd14e41ab 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -2,9 +2,9 @@ import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetada import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; export class IterableEmbeddedMessage { - readonly metadata: IterableEmbeddedMessageMetadata; - readonly elements: IterableEmbeddedMessageElements; - readonly payload: Record; + metadata: IterableEmbeddedMessageMetadata; + elements: IterableEmbeddedMessageElements; + payload: Record; constructor( metadata: IterableEmbeddedMessageMetadata, diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts index 53f0240f0..b42fc25da 100644 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -1 +1,33 @@ -export class IterableEmbeddedMessageMetadata {} +export class IterableEmbeddedMessageMetadata { + static readonly TAG = 'ItblEmbeddedMessageMetadata'; + + readonly messageId: string; + readonly placementId: number; + readonly campaignId?: number; + readonly isProof: boolean; + + constructor( + messageId: string, + placementId: number, + campaignId: number | undefined, + isProof: boolean = false + ) { + this.messageId = messageId; + this.placementId = placementId; + this.campaignId = campaignId; + this.isProof = isProof; + } + static fromDict(dict: { + messageId: string; + placementId: number; + campaignId: number | undefined; + isProof: boolean; + }): IterableEmbeddedMessageMetadata { + return new IterableEmbeddedMessageMetadata( + dict.messageId, + dict.placementId, + dict.campaignId, + dict.isProof + ); + } +} From 0bb9997f495fd31ab3ae728299947d069680bdb9 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Sat, 24 May 2025 11:36:43 -0600 Subject: [PATCH 04/18] feat: adds unit tests for IterableMessageMetadata --- src/__tests__/IterabelEmbeddedMesage.test.ts | 61 +++++++++++++++++++ .../IterableEmbeddedMessageMetadata.ts | 22 ++++--- 2 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 src/__tests__/IterabelEmbeddedMesage.test.ts diff --git a/src/__tests__/IterabelEmbeddedMesage.test.ts b/src/__tests__/IterabelEmbeddedMesage.test.ts new file mode 100644 index 000000000..b21975965 --- /dev/null +++ b/src/__tests__/IterabelEmbeddedMesage.test.ts @@ -0,0 +1,61 @@ +import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import type { EmbeddedMessageMetadataDict } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import { Iterable } from '../core'; + +describe('IterableEmbeddedMessage', () => { + test('should create an instance of IterableEmbeddedMessageMetadata from a dictionary', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_valid_dictionary' + ); + + const dict = { + messageId: '123', + placementId: 456, + campaignId: 789, + isProof: false, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBe(789); + expect(result.isProof).toBe(false); + }); + + test('should handle optional fields', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_optional_fields_omitted' + ); + + const dict = { + messageId: '123', + placementId: 456, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBeUndefined(); + expect(result.isProof).toBe(false); + }); + + test('should throw an error if messageId is not provided', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_missing_messageId' + ); + + const dict = { + placementId: 456, + }; + + expect(() => { + IterableEmbeddedMessageMetadata.fromDict( + dict as Partial + ); + }).toThrow('messageId and placementId are required'); + }); +}); diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts index b42fc25da..de7b38b0c 100644 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -1,6 +1,4 @@ export class IterableEmbeddedMessageMetadata { - static readonly TAG = 'ItblEmbeddedMessageMetadata'; - readonly messageId: string; readonly placementId: number; readonly campaignId?: number; @@ -17,12 +15,13 @@ export class IterableEmbeddedMessageMetadata { this.campaignId = campaignId; this.isProof = isProof; } - static fromDict(dict: { - messageId: string; - placementId: number; - campaignId: number | undefined; - isProof: boolean; - }): IterableEmbeddedMessageMetadata { + + static fromDict( + dict: Partial + ): IterableEmbeddedMessageMetadata { + if (!dict.messageId || !dict.placementId) { + throw new Error('messageId and placementId are required'); + } return new IterableEmbeddedMessageMetadata( dict.messageId, dict.placementId, @@ -31,3 +30,10 @@ export class IterableEmbeddedMessageMetadata { ); } } + +export interface EmbeddedMessageMetadataDict { + messageId: string; + placementId: number; + campaignId?: number; + isProof?: boolean; +} From ac62585efb13122a5156209f9be1403cf9917ac7 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Sat, 24 May 2025 11:54:52 -0600 Subject: [PATCH 05/18] feat: adds comments to IterableEmbeddedMessageMetadata --- ...=> IterableEmbeddedMesageMetadata.test.ts} | 0 .../IterableEmbeddedMessageMetadata.ts | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+) rename src/__tests__/{IterabelEmbeddedMesage.test.ts => IterableEmbeddedMesageMetadata.test.ts} (100%) diff --git a/src/__tests__/IterabelEmbeddedMesage.test.ts b/src/__tests__/IterableEmbeddedMesageMetadata.test.ts similarity index 100% rename from src/__tests__/IterabelEmbeddedMesage.test.ts rename to src/__tests__/IterableEmbeddedMesageMetadata.test.ts diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts index de7b38b0c..45eaa1def 100644 --- a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -1,9 +1,27 @@ +/** + * Metadata for an embedded message. + */ export class IterableEmbeddedMessageMetadata { + /** The ID for the embedded message */ readonly messageId: string; + + /** The placement ID for the embedded message */ readonly placementId: number; + + /** The campaign ID for the embedded message */ readonly campaignId?: number; + + /** Whether the embedded message is a proof */ readonly isProof: boolean; + /** + * Constructs an instance of IterableEmbeddedMessageMetadata. + * + * @param messageId - The ID for the embedded message. + * @param placementId - The placement ID for the embedded message. + * @param campaignId - The campaign ID for the embedded message. + * @param isProof - Whether the embedded message is a proof. + */ constructor( messageId: string, placementId: number, @@ -16,6 +34,14 @@ export class IterableEmbeddedMessageMetadata { 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 { @@ -31,6 +57,9 @@ export class IterableEmbeddedMessageMetadata { } } +/** + * An interface defining the dictionary object containing the metadata properties for an embedded message. + */ export interface EmbeddedMessageMetadataDict { messageId: string; placementId: number; From 626a256515ef9f40f5a7be25f9b9e853ea905cf8 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Sat, 24 May 2025 13:02:27 -0600 Subject: [PATCH 06/18] feat: adds IterableEmbeddedMessageElements class --- .../classes/IterableEmbeddedMessageButton.ts | 1 + .../IterableEmbeddedMessageDefaultAction.ts | 1 + .../IterableEmbeddedMessageElements.ts | 31 ++++++++++++++++++- .../classes/IterableEmbeddedMessageText.ts | 1 + 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/embedded/classes/IterableEmbeddedMessageButton.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts create mode 100644 src/embedded/classes/IterableEmbeddedMessageText.ts diff --git a/src/embedded/classes/IterableEmbeddedMessageButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts new file mode 100644 index 000000000..1db5528b8 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -0,0 +1 @@ +export class IterableEmbeddedMessageButton {} diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts new file mode 100644 index 000000000..e536203fc --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -0,0 +1 @@ +export class IterableEmbeddedMessageDefaultAction {} diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index 22eedf6a8..334fb2980 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -1 +1,30 @@ -export class IterableEmbeddedMessageElements {} +import { IterableEmbeddedMessageDefaultAction } from './IterableEmbeddedMessageDefaultAction'; +import { IterableEmbeddedMessageButton } from './IterableEmbeddedMessageButton'; +import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; + +export class IterableEmbeddedMessageElements { + readonly title: string; + readonly body: string; + readonly mediaUrl?: string; + readonly mediaUrlCaption?: string; + readonly defaultAction?: IterableEmbeddedMessageDefaultAction; + readonly buttons?: IterableEmbeddedMessageButton[]; + readonly text?: IterableEmbeddedMessageText[]; + constructor( + title: string, + body: string, + mediaUrl: string | undefined, + mediaUrlCaption: string | undefined, + defaultAction: IterableEmbeddedMessageDefaultAction | undefined, + buttons: IterableEmbeddedMessageButton[] | undefined, + text: IterableEmbeddedMessageText[] | undefined + ) { + this.title = title; + this.body = body; + this.mediaUrl = mediaUrl; + this.mediaUrlCaption = mediaUrlCaption; + this.defaultAction = defaultAction; + this.buttons = buttons; + this.text = text; + } +} diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts new file mode 100644 index 000000000..9623d8837 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -0,0 +1 @@ +export class IterableEmbeddedMessageText {} From aacfeaea25899c0cb67c93a5235391c32ac78a06 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 08:17:24 -0600 Subject: [PATCH 07/18] feat: adds IterableEmbeddedMessageDefaultAction and associated tests --- .../IterableEmbeddedMesageMetadata.test.ts | 5 +-- ...erableEmbeddedMessageDefaultAction.test.ts | 42 +++++++++++++++++++ .../IterableEmbeddedMessageDefaultAction.ts | 24 ++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts diff --git a/src/__tests__/IterableEmbeddedMesageMetadata.test.ts b/src/__tests__/IterableEmbeddedMesageMetadata.test.ts index b21975965..f57786a9d 100644 --- a/src/__tests__/IterableEmbeddedMesageMetadata.test.ts +++ b/src/__tests__/IterableEmbeddedMesageMetadata.test.ts @@ -1,5 +1,4 @@ import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; -import type { EmbeddedMessageMetadataDict } from '../embedded/classes/IterableEmbeddedMessageMetadata'; import { Iterable } from '../core'; describe('IterableEmbeddedMessage', () => { @@ -53,9 +52,7 @@ describe('IterableEmbeddedMessage', () => { }; expect(() => { - IterableEmbeddedMessageMetadata.fromDict( - dict as Partial - ); + IterableEmbeddedMessageMetadata.fromDict(dict); }).toThrow('messageId and placementId are required'); }); }); diff --git a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts new file mode 100644 index 000000000..4a590747a --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts @@ -0,0 +1,42 @@ +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 action = new IterableEmbeddedMessageDefaultAction( + 'openUrl', + 'https://example.com' + ); + 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/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts index e536203fc..7625383f3 100644 --- a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -1 +1,23 @@ -export class IterableEmbeddedMessageDefaultAction {} +export class IterableEmbeddedMessageDefaultAction { + readonly type: string; + readonly data?: string; + + constructor(type: string, data?: string) { + this.type = type; + this.data = data; + } + + static fromDict( + dict: Partial + ): IterableEmbeddedMessageDefaultAction { + if (!dict.type) { + throw new Error('type is required'); + } + return new IterableEmbeddedMessageDefaultAction(dict.type, dict.data); + } +} + +export interface EmbeddedMessageDefaultActionDict { + type: string; + data?: string; +} From 78c92f8cb6c7439e4104b945d1c27380db136f94 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 12:46:13 -0600 Subject: [PATCH 08/18] feat: adds docs to IterableEmbeddedMessageDefaultAction --- .../IterableEmbeddedMessageDefaultAction.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts index 7625383f3..486cbbb89 100644 --- a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -1,12 +1,33 @@ 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 { @@ -17,6 +38,9 @@ export class IterableEmbeddedMessageDefaultAction { } } +/** + * An interface defining the dictionary object containing the properties for the embedded message default action. + */ export interface EmbeddedMessageDefaultActionDict { type: string; data?: string; From 023ed302cefd41e0aa7aee15bbf20d5d53107a1a Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 12:58:37 -0600 Subject: [PATCH 09/18] feat: adds IterableEmbeddedMessageText class --- .../classes/IterableEmbeddedMessageText.ts | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts index 9623d8837..98c6a1b87 100644 --- a/src/embedded/classes/IterableEmbeddedMessageText.ts +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -1 +1,45 @@ -export class IterableEmbeddedMessageText {} +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; +} From 0d4b4b3ea912e52fa261ec80705ce21aaa38d4e3 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 13:07:41 -0600 Subject: [PATCH 10/18] feat: adds unit tests for IterableEmbeddedMessageText --- ...erableEmbeddedMessageDefaultAction.test.ts | 6 +-- .../IterableEmbeddedMessageText.test.ts | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/IterableEmbeddedMessageText.test.ts diff --git a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts index 4a590747a..bf99bc5b8 100644 --- a/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts +++ b/src/__tests__/IterableEmbeddedMessageDefaultAction.test.ts @@ -7,10 +7,8 @@ describe('IterableEmbeddedMessageDefaultAction', () => { 'iterableEmbeddedMessageDefaultAction_fromDict_valid_dictionary' ); - const action = new IterableEmbeddedMessageDefaultAction( - 'openUrl', - 'https://example.com' - ); + 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'); diff --git a/src/__tests__/IterableEmbeddedMessageText.test.ts b/src/__tests__/IterableEmbeddedMessageText.test.ts new file mode 100644 index 000000000..744f35c8a --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageText.test.ts @@ -0,0 +1,49 @@ +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 c30f78ac16f3b663cbd54cb0fb4cbffb2d9af5a1 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 13:09:53 -0600 Subject: [PATCH 11/18] feat: adds unit tests for IterableEmbeddedMessageText --- .../IterableEmbeddedMessageText.test.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/__tests__/IterableEmbeddedMessageText.test.ts b/src/__tests__/IterableEmbeddedMessageText.test.ts index 744f35c8a..10b3e2ffe 100644 --- a/src/__tests__/IterableEmbeddedMessageText.test.ts +++ b/src/__tests__/IterableEmbeddedMessageText.test.ts @@ -5,12 +5,7 @@ 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 dict = { id: 'text-123', text: 'Hello World!', type: 'heading' }; const text = IterableEmbeddedMessageText.fromDict(dict); expect(text).toBeInstanceOf(IterableEmbeddedMessageText); @@ -22,10 +17,7 @@ describe('IterableEmbeddedMessageText', () => { 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 dict = { id: 'text-123' }; const text = IterableEmbeddedMessageText.fromDict(dict); expect(text).toBeInstanceOf(IterableEmbeddedMessageText); @@ -37,10 +29,7 @@ describe('IterableEmbeddedMessageText', () => { 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', - }; + const dict = { text: 'Hello World!', type: 'heading' }; expect(() => IterableEmbeddedMessageText.fromDict(dict)).toThrow( 'id is required' From 1ce1e39a3577c9ad6523e079a55101782ddd57e5 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 13:23:18 -0600 Subject: [PATCH 12/18] feat: adds IterableEmbeddedMessageButton class --- .../classes/IterableEmbeddedMessageButton.ts | 33 ++++++++++++++++++- ...ableEmbeddedMessageElementsButtonAction.ts | 26 +++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts diff --git a/src/embedded/classes/IterableEmbeddedMessageButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts index 1db5528b8..11170d7a1 100644 --- a/src/embedded/classes/IterableEmbeddedMessageButton.ts +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -1 +1,32 @@ -export class IterableEmbeddedMessageButton {} +import { IterableEmbeddedMessageElementsButtonAction } from './IterableEmbeddedMessageElementsButtonAction'; + +export class IterableEmbeddedMessageButton { + readonly id: string; + readonly title?: string; + readonly action?: IterableEmbeddedMessageElementsButtonAction; + + constructor( + id: string, + title?: string, + action?: IterableEmbeddedMessageElementsButtonAction + ) { + this.id = id; + this.title = title; + this.action = action; + } + + static fromDict( + dict: Partial + ): IterableEmbeddedMessageButton { + if (!dict.id) { + throw new Error('id is required'); + } + return new IterableEmbeddedMessageButton(dict.id, dict.title, dict.action); + } +} + +export interface EmbeddedMessageButtonDict { + 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..4f3a9988e --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElementsButtonAction.ts @@ -0,0 +1,26 @@ +export class IterableEmbeddedMessageElementsButtonAction { + readonly type: string; + readonly data?: string; + + constructor(type: string, data?: string) { + this.type = type; + this.data = data; + } + + static fromDict( + dict: Partial + ): IterableEmbeddedMessageElementsButtonAction { + if (!dict.type) { + throw new Error('type is required'); + } + return new IterableEmbeddedMessageElementsButtonAction( + dict.type, + dict.data + ); + } +} + +interface EmbeddedMessageButtonActionDict { + type: string; + data?: string; +} From 547377cb8e199b38c6ec95fa1d233d6e6d892cfa Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 14:56:05 -0600 Subject: [PATCH 13/18] feat: adds unit tests for IterableEmbeddedMessageButton --- .../IterableEmbeddedMessageButton.test.ts | 91 +++++++++++++++++++ ...mbeddedMessageElementsButtonAction.test.ts | 40 ++++++++ .../classes/IterableEmbeddedMessageButton.ts | 5 +- 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/IterableEmbeddedMessageButton.test.ts create mode 100644 src/__tests__/IterableEmbeddedMessageElementsButtonAction.test.ts diff --git a/src/__tests__/IterableEmbeddedMessageButton.test.ts b/src/__tests__/IterableEmbeddedMessageButton.test.ts new file mode 100644 index 000000000..f96488463 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageButton.test.ts @@ -0,0 +1,91 @@ +import { IterableEmbeddedMessageButton } from '../embedded/classes/IterableEmbeddedMessageButton'; +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 = IterableEmbeddedMessageButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + 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 = IterableEmbeddedMessageButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBeUndefined(); + expect(button.action).toBeUndefined(); + }); + + it('should create an instance with title but no action', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_title_only'); + + const dict = { + id: 'button-123', + title: 'Click Me!', + }; + + const button = IterableEmbeddedMessageButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + expect(button.id).toBe('button-123'); + expect(button.title).toBe('Click Me!'); + expect(button.action).toBeUndefined(); + }); + + it('should throw an error if id is missing in fromDict', () => { + Iterable.logger.log('iterableEmbeddedMessageButton_fromDict_missing_id'); + + const dict = { + title: 'Click Me!', + action: { type: 'openUrl', data: 'https://example.com' }, + }; + + expect(() => IterableEmbeddedMessageButton.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 = IterableEmbeddedMessageButton.fromDict(dict); + + expect(button).toBeInstanceOf(IterableEmbeddedMessageButton); + 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'); + }); +}); diff --git a/src/embedded/classes/IterableEmbeddedMessageButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts index 11170d7a1..b4efff426 100644 --- a/src/embedded/classes/IterableEmbeddedMessageButton.ts +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -21,7 +21,10 @@ export class IterableEmbeddedMessageButton { if (!dict.id) { throw new Error('id is required'); } - return new IterableEmbeddedMessageButton(dict.id, dict.title, dict.action); + const action = dict.action + ? IterableEmbeddedMessageElementsButtonAction.fromDict(dict.action) + : undefined; + return new IterableEmbeddedMessageButton(dict.id, dict.title, action); } } From 76ef3b91cfb4561149a81239104fdd4e6407c10f Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 16:09:02 -0600 Subject: [PATCH 14/18] feat: adds IterableEmbeddedMessageElements class --- .../IterableEmbeddedMessageElements.ts | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedMessageElements.ts b/src/embedded/classes/IterableEmbeddedMessageElements.ts index 334fb2980..a8b52170e 100644 --- a/src/embedded/classes/IterableEmbeddedMessageElements.ts +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -3,21 +3,22 @@ import { IterableEmbeddedMessageButton } from './IterableEmbeddedMessageButton'; import { IterableEmbeddedMessageText } from './IterableEmbeddedMessageText'; export class IterableEmbeddedMessageElements { - readonly title: string; - readonly body: string; + readonly title?: string; + readonly body?: string; readonly mediaUrl?: string; readonly mediaUrlCaption?: string; readonly defaultAction?: IterableEmbeddedMessageDefaultAction; readonly buttons?: IterableEmbeddedMessageButton[]; readonly text?: IterableEmbeddedMessageText[]; + constructor( - title: string, - body: string, - mediaUrl: string | undefined, - mediaUrlCaption: string | undefined, - defaultAction: IterableEmbeddedMessageDefaultAction | undefined, - buttons: IterableEmbeddedMessageButton[] | undefined, - text: IterableEmbeddedMessageText[] | undefined + title?: string, + body?: string, + mediaUrl?: string, + mediaUrlCaption?: string, + defaultAction?: IterableEmbeddedMessageDefaultAction, + buttons?: IterableEmbeddedMessageButton[], + text?: IterableEmbeddedMessageText[] ) { this.title = title; this.body = body; @@ -27,4 +28,44 @@ export class IterableEmbeddedMessageElements { this.buttons = buttons; this.text = text; } + + 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) => + IterableEmbeddedMessageButton.fromDict(button) + ); + + const text = dict.text?.map((text) => + IterableEmbeddedMessageText.fromDict(text) + ); + + return new IterableEmbeddedMessageElements( + title, + body, + mediaUrl, + mediaUrlCaption, + defaultAction, + buttons, + text + ); + } +} + +export interface EmbeddedMessageElementsDict { + title?: string; + body?: string; + mediaUrl?: string; + mediaUrlCaption?: string; + defaultAction?: IterableEmbeddedMessageDefaultAction; + buttons?: IterableEmbeddedMessageButton[]; + text?: IterableEmbeddedMessageText[]; } From a07277fb7f648ae549279f14b611cd844d17d8bc Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 16:35:39 -0600 Subject: [PATCH 15/18] feat: adds unit tests for IterableEmbeddedMessageElements --- .../IterableEmbeddedMessageElements.test.ts | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessageElements.test.ts diff --git a/src/__tests__/IterableEmbeddedMessageElements.test.ts b/src/__tests__/IterableEmbeddedMessageElements.test.ts new file mode 100644 index 000000000..e66e42b45 --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessageElements.test.ts @@ -0,0 +1,212 @@ +import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; +import { IterableEmbeddedMessageDefaultAction } from '../embedded/classes/IterableEmbeddedMessageDefaultAction'; +import { IterableEmbeddedMessageButton } from '../embedded/classes/IterableEmbeddedMessageButton'; +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 IterableEmbeddedMessageButton; + expect(firstButton).toBeInstanceOf(IterableEmbeddedMessageButton); + 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 IterableEmbeddedMessageButton; + expect(secondButton).toBeInstanceOf(IterableEmbeddedMessageButton); + 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); + }); +}); From 1e2a3a96609415fed67e32483db5e5fa7ff5ec3c Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 16:56:32 -0600 Subject: [PATCH 16/18] feat: adds dictionary processing to IterableEmbeddedPlacement and IterableEmbeddedMessage --- .../classes/IterableEmbeddedMessage.ts | 26 ++++++++++++++++--- .../classes/IterableEmbeddedPlacement.ts | 25 ++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts index fd14e41ab..0625d0361 100644 --- a/src/embedded/classes/IterableEmbeddedMessage.ts +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -3,16 +3,34 @@ import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElemen export class IterableEmbeddedMessage { metadata: IterableEmbeddedMessageMetadata; - elements: IterableEmbeddedMessageElements; - payload: Record; + elements?: IterableEmbeddedMessageElements; + payload?: Record; constructor( metadata: IterableEmbeddedMessageMetadata, - elements: IterableEmbeddedMessageElements, - payload: Record + elements?: IterableEmbeddedMessageElements, + payload?: Record ) { this.metadata = metadata; this.elements = elements; this.payload = payload; } + + 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); + } +} + +interface EmbeddedMessageDict { + metadata: IterableEmbeddedMessageMetadata; + elements: IterableEmbeddedMessageElements; + payload: Record; } diff --git a/src/embedded/classes/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts index 1173ffad7..c2e7e92f3 100644 --- a/src/embedded/classes/IterableEmbeddedPlacement.ts +++ b/src/embedded/classes/IterableEmbeddedPlacement.ts @@ -7,10 +7,31 @@ import { IterableEmbeddedMessage } from './IterableEmbeddedMessage'; export class IterableEmbeddedPlacement { readonly placementId: number; - readonly messages: IterableEmbeddedMessage[]; + readonly messages?: IterableEmbeddedMessage[]; - constructor(placementId: number, messages: IterableEmbeddedMessage[]) { + constructor(placementId: number, messages?: IterableEmbeddedMessage[]) { this.placementId = placementId; this.messages = messages; } + + static fromDict( + dict: Partial + ): IterableEmbeddedPlacement { + if (!dict.placementId) { + throw new Error('placementId is required'); + } + + const placementId = dict.placementId; + const messages = dict.messages + ? dict.messages?.map((message) => + IterableEmbeddedMessage.fromDict(message) + ) + : undefined; + return new IterableEmbeddedPlacement(placementId, messages); + } +} + +interface EmbeddedPlacementDict { + placementId: number; + messages: IterableEmbeddedMessage[]; } From 9ce071e4928f8f5268673174c5c5db0526485a6e Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 17:03:25 -0600 Subject: [PATCH 17/18] feat: adds unit tests for IterableEmbeddedMessage --- src/__tests__/IterableEmbeddedMessage.test.ts | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessage.test.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', + }); + }); +}); From ee001debb4d07061ed24e460a585e3084ff27524 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Tue, 27 May 2025 17:18:20 -0600 Subject: [PATCH 18/18] feat: adds unit tests for IterableEmbeddedPlacement --- .../IterableEmbeddedPlacement.test.ts | 104 ++++++++++++++++++ .../classes/IterableEmbeddedPlacement.ts | 2 +- 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/IterableEmbeddedPlacement.test.ts diff --git a/src/__tests__/IterableEmbeddedPlacement.test.ts b/src/__tests__/IterableEmbeddedPlacement.test.ts new file mode 100644 index 000000000..65c9b110a --- /dev/null +++ b/src/__tests__/IterableEmbeddedPlacement.test.ts @@ -0,0 +1,104 @@ +import { IterableEmbeddedPlacement } from '../embedded/classes/IterableEmbeddedPlacement'; +import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedPlacement', () => { + it('should create an instance with placementId and messages', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_with_messages'); + + const dict = { + placementId: 123, + messages: [ + { + metadata: { + messageId: 'msg-1', + placementId: 123, + isProof: false, + }, + elements: { + title: 'First Message', + body: 'Body of first message', + }, + }, + { + metadata: { + messageId: 'msg-2', + placementId: 123, + isProof: false, + }, + elements: { + title: 'Second Message', + body: 'Body of second message', + }, + }, + ], + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(123); + expect(placement.messages).toBeDefined(); + expect(placement.messages!.length).toBe(2); + const messages = placement.messages as [ + IterableEmbeddedMessage, + IterableEmbeddedMessage, + ]; + expect(messages[0]).toBeInstanceOf(IterableEmbeddedMessage); + expect(messages[1]).toBeInstanceOf(IterableEmbeddedMessage); + expect(messages[0].metadata.messageId).toBe('msg-1'); + expect(messages[1].metadata.messageId).toBe('msg-2'); + }); + + it('should create an instance with only placementId', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_placementId_only'); + + const dict = { + placementId: 456, + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(456); + expect(placement.messages).toBeUndefined(); + }); + + it('should throw an error if placementId is missing', () => { + Iterable.logger.log( + 'iterableEmbeddedPlacement_fromDict_missing_placementId' + ); + + const dict = { + messages: [ + { + metadata: { + messageId: 'msg-1', + placementId: 123, + isProof: false, + }, + }, + ], + }; + + expect(() => IterableEmbeddedPlacement.fromDict(dict)).toThrow( + 'placementId is required' + ); + }); + + it('should handle empty messages array', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_empty_messages'); + + const dict = { + placementId: 789, + messages: [], + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(789); + expect(placement.messages).toBeDefined(); + expect(placement.messages!.length).toBe(0); + }); +}); diff --git a/src/embedded/classes/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts index c2e7e92f3..ea79e8854 100644 --- a/src/embedded/classes/IterableEmbeddedPlacement.ts +++ b/src/embedded/classes/IterableEmbeddedPlacement.ts @@ -33,5 +33,5 @@ export class IterableEmbeddedPlacement { interface EmbeddedPlacementDict { placementId: number; - messages: IterableEmbeddedMessage[]; + messages?: IterableEmbeddedMessage[]; }