diff --git a/src/__tests__/IterableEmbeddedMesageMetadata.test.ts b/src/__tests__/IterableEmbeddedMesageMetadata.test.ts new file mode 100644 index 000000000..f57786a9d --- /dev/null +++ b/src/__tests__/IterableEmbeddedMesageMetadata.test.ts @@ -0,0 +1,58 @@ +import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import { Iterable } from '../core'; + +describe('IterableEmbeddedMessage', () => { + test('should create an instance of IterableEmbeddedMessageMetadata from a dictionary', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_valid_dictionary' + ); + + const dict = { + messageId: '123', + placementId: 456, + campaignId: 789, + isProof: false, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBe(789); + expect(result.isProof).toBe(false); + }); + + test('should handle optional fields', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_optional_fields_omitted' + ); + + const dict = { + messageId: '123', + placementId: 456, + }; + + const result = IterableEmbeddedMessageMetadata.fromDict(dict); + + expect(result).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(result.messageId).toBe('123'); + expect(result.placementId).toBe(456); + expect(result.campaignId).toBeUndefined(); + expect(result.isProof).toBe(false); + }); + + test('should throw an error if messageId is not provided', () => { + Iterable.logger.log( + 'iterableEmbeddedMessageMetadata_fromDict_missing_messageId' + ); + + const dict = { + placementId: 456, + }; + + expect(() => { + IterableEmbeddedMessageMetadata.fromDict(dict); + }).toThrow('messageId and placementId are required'); + }); +}); diff --git a/src/__tests__/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/__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__/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..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); + }); +}); 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/__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' + ); + }); +}); 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/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..0625d0361 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -0,0 +1,36 @@ +import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; + +export class IterableEmbeddedMessage { + metadata: IterableEmbeddedMessageMetadata; + elements?: IterableEmbeddedMessageElements; + payload?: Record; + + constructor( + metadata: IterableEmbeddedMessageMetadata, + 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/IterableEmbeddedMessageButton.ts b/src/embedded/classes/IterableEmbeddedMessageButton.ts new file mode 100644 index 000000000..b4efff426 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageButton.ts @@ -0,0 +1,35 @@ +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'); + } + const action = dict.action + ? IterableEmbeddedMessageElementsButtonAction.fromDict(dict.action) + : undefined; + return new IterableEmbeddedMessageButton(dict.id, dict.title, action); + } +} + +export interface EmbeddedMessageButtonDict { + id: string; + title?: string; + action?: IterableEmbeddedMessageElementsButtonAction; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts new file mode 100644 index 000000000..486cbbb89 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageDefaultAction.ts @@ -0,0 +1,47 @@ +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..a8b52170e --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageElements.ts @@ -0,0 +1,71 @@ +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, + mediaUrlCaption?: string, + defaultAction?: IterableEmbeddedMessageDefaultAction, + buttons?: IterableEmbeddedMessageButton[], + text?: IterableEmbeddedMessageText[] + ) { + this.title = title; + this.body = body; + this.mediaUrl = mediaUrl; + this.mediaUrlCaption = mediaUrlCaption; + this.defaultAction = defaultAction; + 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[]; +} 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; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageMetadata.ts b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts new file mode 100644 index 000000000..45eaa1def --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageMetadata.ts @@ -0,0 +1,68 @@ +/** + * Metadata for an embedded message. + */ +export class IterableEmbeddedMessageMetadata { + /** The ID for the embedded message */ + readonly messageId: string; + + /** The placement ID for the embedded message */ + readonly placementId: number; + + /** The campaign ID for the embedded message */ + readonly campaignId?: number; + + /** Whether the embedded message is a proof */ + readonly isProof: boolean; + + /** + * Constructs an instance of IterableEmbeddedMessageMetadata. + * + * @param messageId - The ID for the embedded message. + * @param placementId - The placement ID for the embedded message. + * @param campaignId - The campaign ID for the embedded message. + * @param isProof - Whether the embedded message is a proof. + */ + constructor( + messageId: string, + placementId: number, + campaignId: number | undefined, + isProof: boolean = false + ) { + this.messageId = messageId; + this.placementId = placementId; + this.campaignId = campaignId; + this.isProof = isProof; + } + + /** + * Creates an instance of `IterableEmbeddedMessageMetadata` from a dictionary object. + * + * @param dict - The dictionary objectcontaining the metadata properties. + * This corresponds to the properties in {@link IterableEmbeddedMessageMetadata} + * + * @returns A new instance of `IterableEmbeddedMessageMetadata` with the provided properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedMessageMetadata { + if (!dict.messageId || !dict.placementId) { + throw new Error('messageId and placementId are required'); + } + return new IterableEmbeddedMessageMetadata( + dict.messageId, + dict.placementId, + dict.campaignId, + dict.isProof + ); + } +} + +/** + * An interface defining the dictionary object containing the metadata properties for an embedded message. + */ +export interface EmbeddedMessageMetadataDict { + messageId: string; + placementId: number; + campaignId?: number; + isProof?: boolean; +} diff --git a/src/embedded/classes/IterableEmbeddedMessageText.ts b/src/embedded/classes/IterableEmbeddedMessageText.ts new file mode 100644 index 000000000..98c6a1b87 --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessageText.ts @@ -0,0 +1,45 @@ +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/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts index 6a02aabfe..ea79e8854 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,31 @@ export class IterableEmbeddedPlacement { readonly placementId: number; - constructor(placementId: number) { + readonly 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[]; }