UNPKG

@causalfoundry/js-sdk

Version:

Causal Foundry WEB SDK (JS/TS)

666 lines (511 loc) 24.2 kB
/** * @jest-environment jsdom */ import CfCore from "../../../core/CfCore" import CatalogRepository, {CatalogRepositoryConfig} from "../../../core/repositories/catalog/CatalogRepository"; import ECommerce from "../../ECommerce"; import { DeliveryAction, ECommerceTypes } from "../../ECommerce/typings"; import Navigation from ".." import { AppAction, MediaType, ContentBlock, IdentityAction, MediaAction, NavigationTypes, NudgeAction, NudgeResponseType, RateType, UserInfo, SearchModuleType, } from "../typings"; import { ItemType } from "../../ECommerce/typings"; let windowSpy let mockSender; class MockImpressionsDetector { start() { } stop() { } restart() { } } describe('Navigation', () => { beforeEach(() => { mockSender = { add() { return false } } windowSpy = jest.spyOn(window, "window", "get"); }) afterEach(() => { // to remove the singleton instance (CfCore as any).instance = null }) describe('Identification', () => { it('Should deliver events with deviceId instead of userId before identification', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) Navigation.logPageEvent({ path: '/mypath', title: 'page-title' }) expect(senderSpy).toBeCalledWith( expect.stringMatching(DEFAULT_DEVICE_ID), expect.anything(), expect.anything(), expect.anything()) }) it('Should deliver events with userId once the user has been logged', () => { const DEFAULT_USER_ID = 'dummy_user' const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) bsCore.login(DEFAULT_USER_ID) Navigation.logPageEvent({ path: '/mypath', title: 'page-title' }) expect(senderSpy).toBeCalledWith( expect.stringMatching(DEFAULT_USER_ID), expect.stringMatching(DEFAULT_DEVICE_ID), expect.anything(), expect.anything()) }) it('Should stop using user_id after logout', () => { const DEFAULT_USER_ID = 'dummy_user' const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) bsCore.login(DEFAULT_USER_ID) Navigation.logPageEvent({ path: '/mypath', title: 'page-title' }) // bsCore.logout() // Navigation.logPageEvent({ path: '/mypath', title: 'page-title' }) expect(senderSpy).lastCalledWith( expect.stringMatching(DEFAULT_USER_ID), expect.stringMatching(DEFAULT_DEVICE_ID), expect.anything(), expect.anything()) }) }) describe('Navigation', () => { it('Should deliver an "App" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { action: AppAction.Background, start_time : 0 } Navigation.logIngestEvent(NavigationTypes.App, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.App, props: event }), expect.anything()) }) it('Should deliver a "Media" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { type: MediaType.Image, id: 'id-media-event', action: MediaAction.View, } const sentEvent = { type: MediaType.Image, time: 0, id_source: 'id-media-event', id: 'image_id-media-event', action: MediaAction.Play, } Navigation.logIngestEvent(NavigationTypes.Media, event,) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Media, props: sentEvent }), expect.anything()) }) it('Should deliver a "Media" event with default type', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { type: MediaType.Image, id: 'id-media-event', action: MediaAction.View, } Navigation.logIngestEvent(NavigationTypes.Media, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { block: ContentBlock.Core }), expect.anything()) }) it('Should deliver a "Rate" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { rate_value: 4, type: RateType.App, subject_id: 'app_id' } Navigation.logIngestEvent(NavigationTypes.Rate, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { block: ContentBlock.Core }), expect.anything()) }) it('Should deliver a "Rate" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { rate_value: 7, type: RateType.App, subject_id: 'app_id' } expect(() => Navigation.logIngestEvent(NavigationTypes.Rate, event)).toThrowError() }) it('Should complain when mandatory fields are missing in media catalog', async () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const defaults: CatalogRepositoryConfig = { flushTime: 10 * 1000 } const catalogRepository = new CatalogRepository( `dummy-url`, mockSender, defaults ) Navigation.init(catalogRepository) const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { type: MediaType.Video, time: 1000, id: 'id-media-event', action: MediaAction.Play, } await expect(Navigation.logIngestEvent(NavigationTypes.Media, event)).rejects.toThrowError() }) it('Should deliver a "Media" event with e-elearning type', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { contentBlock: ContentBlock.ELearning, type: MediaType.Image, id: 'id-media-event', action: MediaAction.View, } Navigation.logIngestEvent(NavigationTypes.Media, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { block: ContentBlock.ELearning }), expect.anything()) }) it('Should deliver a "Page" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title' } Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Page, props: event }), expect.anything()) }) it('Should deliver a "Login" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const props: UserInfo = { action: IdentityAction.Login, user_props: {} } Navigation.logIdentifyEvent(props) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Identify, props }), expect.anything()) }) it('Should deliver a "Delivery" event with the content block set to ECommerce - no call to setCurrentBlock', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { id: "1", action: DeliveryAction.Delivered, order_id: '11', } ECommerce.logDeliveryEvent(event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: ECommerceTypes.Delivery, block: ContentBlock.ECommerce }), expect.anything()) }) it('Should deliver a "Delivery" event with the contentBlock set to ECommerce - call to setCurrentBlock', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { id: "1", action: DeliveryAction.Delivered, order_id: '11', } Navigation.setCurrentBlock(ContentBlock.ELearning) ECommerce.logDeliveryEvent(event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: ECommerceTypes.Delivery, block: ContentBlock.ECommerce }), expect.anything()) }) it('Should deliver a "Page" event with a default contentBlock that have been overwritten', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.ELearning, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title' } Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Page, block: ContentBlock.ELearning }), expect.anything()) }) it('Should deliver a "Page" event with a default contentBlock that have been overwritten', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.ELearning, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title' } Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Page, block: ContentBlock.ELearning }), expect.anything()) }) it('Should deliver a "Page" event after setting explicitily the block name', async () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title' } Navigation.setCurrentBlock(ContentBlock.ECommerce) await new Promise((r) => setTimeout(r, 20)); Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Page, block: ContentBlock.ECommerce }), expect.anything()) }) it('Should discard page events where duration is 0', async () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title', duration: 0 } Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledTimes(0) }) it('Should not discard page events if duration is not present', async () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { path: '/mypath', title: 'page-title' } Navigation.logIngestEvent(NavigationTypes.Page, event) expect(senderSpy).toBeCalledTimes(1) }) it('Should deliver a "PushNotification" event', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { nudge_id: 111, type: NudgeResponseType.Push, response: { action: NudgeAction.Discard }, resolved_action: {} } Navigation.logPushNotificationEvent(event) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.NudgeResponse, props: event }), expect.anything()) }) it('Should deliver a "Search" event with a specific content block', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const userEvent = { module: SearchModuleType.Core, page: 0, query: 'query-string', results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}], filter: { param: '11' } } const internalEvent = { page: 0, query: 'query-string', results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}], filter: { param: '11' } } Navigation.logIngestEvent(NavigationTypes.Search, userEvent,true) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Search, props: expect.objectContaining(internalEvent) // we don't care the id in this assertion }), expect.anything()) // assert that the SDK has added the search ID expect((senderSpy.mock.calls[0][2] as any).props.id).toBeDefined() const eventDelivered = { block: ContentBlock.ECommerce } expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining(eventDelivered), expect.anything()) }) it('Should deliver a "Search" event for the default content block', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const event = { module: SearchModuleType.Core, page: 0, query: 'query-string', results_list: ['1', '2'], filter: { param: '11' } } const eventDelivered = { block: ContentBlock.Core } Navigation.logIngestEvent(NavigationTypes.Search, event, true) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining(eventDelivered), expect.anything()) // assert that the SDK has added the search ID expect((senderSpy.mock.calls[0][2] as any).props.id).toBeDefined() }) it('Should deliver a "Search" event adapting list of ID to list of TypedItems', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const userEvent = { contentBlock: ContentBlock.ECommerce, page: 0, query: 'query-string', results_list: ['1', '2'], filter: { param: '11' } } const internalEvent = { module: SearchModuleType.Core, page: 0, query: 'query-string', results_list: [{id:'1', type: ItemType.Drug}, {id: '2', type: ItemType.Drug}], filter: { param: '11' } } Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true) expect(senderSpy).toBeCalledWith( expect.anything(), expect.anything(), expect.objectContaining( { type: NavigationTypes.Search, props: expect.objectContaining(internalEvent) // we don't care the id in this assertion }), expect.anything()) }) it('Should throw an exception on "Search" - malformed list of result_ids, first string, rest objects', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const userEvent = { module: SearchModuleType.Core, page: 0, query: 'query-string', results_list: [{id: '2', type: ItemType.Drug}], filter: { param: '11' } } expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError() }) it('Should throw an exception on "Search" - malformed list of result_ids, first object, rest strings', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const userEvent = { module: SearchModuleType.ECommerce, page: 0, query: 'query-string', results_list: [ {id: '2', type: ItemType.Drug}], filter: { param: '11' } } expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError() }) it('Should throw an exception on "Search" - repeated ids', () => { const DEFAULT_DEVICE_ID = 'web_sdk_device_id' const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = CfCore.createInstance(mockSender as any, MockImpressionsDetector, false, ContentBlock.Core, DEFAULT_DEVICE_ID, false) const userEvent = { module: SearchModuleType.ECommerce, page: 0, query: 'query-string', results_list: [], filter: { param: '11' } } expect(async () => Navigation.logIngestEvent(NavigationTypes.Search, userEvent, true)).rejects.toThrowError() }) }) })