@causalfoundry/js-sdk
Version:
Causal Foundry WEB SDK (JS/TS)
650 lines (550 loc) • 22 kB
text/typescript
/**
* @jest-environment jsdom
*/
import CfCore from "../../../core/CfCore"
import ECommerce from "../../ECommerce"
import Navigation from "../../Navigation"
import { EventEmitter } from 'events'
import {
CartAction,
DeliveryAction,
DrugProperties,
ECommerceTypes,
ItemAction,
ListAction,
ListType,
ItemType,
StockStatus,
ScheduleDeliveryAction,
CancelType
} from "../typings";
import {
BloodProperties
} from "../blood.typings"
import { CurrencyCode } from "../../../core/commonTypes"
import { ICatalogRepository } from "../../../core/repositories/catalog/CatalogRepository";
import { MediaType, ContentBlock, MediaCatalog, UserCatalog } from "../../Navigation/typings";
import { toISOLocal } from "../../../utils";
import { ImpressionEventType } from "../../../core/impressions/typings";
import { MedicalEquipmentProperties } from "../medicalEquipment.typings";
import { OxygenProperties } from "../oxygen.typings";
let windowSpy
let mockSender;
let mockImpressionManager = {
on: () => { }
};
let mockCatalogRepository: ICatalogRepository;
const device_id = 'asdf'
describe('ECommerce', () => {
beforeAll(() => {
mockSender = {
add() { return false }
}
const mockCurrencyRepository = {
injectBlood: (itemId: string, properties: BloodProperties) => new Promise(r => r()),
injectDrug: (itemId: string, properties: DrugProperties) => new Promise(r => r()),
injectMedia: (id: string, type: MediaType, properties: MediaCatalog) => { },
injectMedicalEquipment: (itemId: string, properties: MedicalEquipmentProperties) => new Promise(r => r()),
injectOxygen: (itemId: string, properties: OxygenProperties) => new Promise(r => r()),
injectUser: (userId: string, properties: UserCatalog) => new Promise(r => r()),
}
ECommerce.init(mockCurrencyRepository, mockCatalogRepository)
})
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
})
afterEach(() => {
// to remove the singleton instance
(CfCore as any).instance = null
})
it('Should deliver a "Checkout" event', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const cfCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
cart_price: 10,
currency: CurrencyCode.EUR,
tax: 0,
items: [{
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
}],
cart_id: '11',
is_successful: true
}
await ECommerce.logCheckoutEvent(event)
expect(senderSpy).toBeCalledWith(
expect.stringMatching(device_id),
expect.stringMatching(device_id),
expect.objectContaining(
{
type: ECommerceTypes.Checkout,
props: { ...event}
}),
expect.anything())
})
it('Should thrown an exception on "Checkout" event with repeated items', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
cart_price: 10,
currency: CurrencyCode.EUR,
tax: 0,
items: [{
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
},{
id: '111',
price: 89,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
},
],
cart_id: '11',
is_successful: true
}
expect(async () => await ECommerce.logCheckoutEvent(event)).rejects.toThrowError()
})
it('Should trigger an exception delivering a "Checkout" event with blood without meta', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
cart_price: 10,
currency: CurrencyCode.EUR,
tax: 0,
items: [{
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
}, {
id: '222',
price: 90,
type: ItemType.Blood,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
},],
cart_id: '11',
is_successful: true
}
await expect(() => ECommerce.logCheckoutEvent(event)).rejects.toThrowError()
})
it('Should not trigger an exception delivering a "Checkout" event with blood with meta', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
cart_price: 10,
currency: CurrencyCode.EUR,
tax: 0,
items: [{
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
}, {
id: '222',
price: 90,
type: ItemType.Blood,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock,
meta: {
cross_matching: true,
temperature_strips: true,
extra_tests: true,
reason: 'asdf'
}
},],
cart_id: '11',
is_successful: true
}
await expect(() => ECommerce.logCheckoutEvent(event)).not.toThrowError()
})
it('Should trigger an Exception due currency missmatch in a "Checkout" event', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
cart_price: 10,
currency: CurrencyCode.EUR,
tax: 0,
items: [{
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
}, {
id: '111',
type: ItemType.Drug,
price: 90,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.GBP,
stock_status: StockStatus.InStock
},
],
cart_id: '11',
is_successful: true
}
await expect(ECommerce.logCheckoutEvent(event)).rejects.toThrow('currency-missmatch')
})
it('Should deliver a "Delivery" event', () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "1",
action: DeliveryAction.Delivered,
order_id: '11',
}
ECommerce.logDeliveryEvent(event)
expect(senderSpy).toBeCalledWith(
expect.stringMatching(device_id),
expect.stringMatching(device_id),
expect.objectContaining(
{
type: ECommerceTypes.Delivery,
props: event
}),
expect.anything())
})
it('Should deliver a "Cancel" event', () => {
const senderSpy = jest.spyOn(mockSender, 'add')
CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "123",
type: CancelType.Order,
items: [{ id: '111', type: ItemType.Drug }],
reason: "some text",
}
ECommerce.logCancelCheckoutEvent(event)
expect(senderSpy).toBeCalledWith(
expect.stringMatching(device_id),
expect.stringMatching(device_id),
expect.objectContaining(
{
type: ECommerceTypes.Cancel,
props: event
}),
expect.anything())
})
it('Should thrown an exception on "Cancel" event due repeated ids', () => {
const senderSpy = jest.spyOn(mockSender, 'add')
CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
id: "123",
type: CancelType.Order,
items: [{ id: '111', type: ItemType.Drug }, { id: '111', type: ItemType.Drug }],
reason: "some text",
}
expect(async () => ECommerce.logCancelCheckoutEvent(event)).rejects.toThrowError()
})
it('Should throw an error delivering "Cancel" event with invalid types', () => {
const senderSpy = jest.spyOn(mockSender, 'add')
CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
order_id: "123",
types: [ItemType.Blood, ItemType.Electronics, "non-existing-type"],
reason: "some text",
}
expect(() => ECommerce.logCancelOrderEvent(event)).toThrowError()
})
it('Should deliver a "Cart" event', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(
mockSender as any,
mockImpressionManager as any,
false,
ContentBlock.Core,
device_id, false)
const event = {
id: "1",
action: CartAction.AddItem,
cart_price: 10,
currency: CurrencyCode.EUR,
item: {
id: '111',
type: ItemType.Drug,
price: 90,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
},
}
await ECommerce.logCartEvent(event)
expect(senderSpy).toBeCalledWith(
expect.stringMatching(device_id),
expect.stringMatching(device_id),
expect.objectContaining(
{
type: ECommerceTypes.Cart,
props: { ...event}
}),
expect.anything())
})
it('Should trigger an Exception when currencies missmatch in "Cart" event', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(
mockSender as any,
mockImpressionManager as any,
false,
ContentBlock.Core,
device_id, false)
const event = {
id: "1",
action: CartAction.AddItem,
cart_price: 10,
currency: CurrencyCode.BDT,
item: {
id: '111',
price: 90,
type: ItemType.Drug,
quantity: 1,
promo_id: '11111',
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock
},
}
await expect(ECommerce.logCartEvent(event)).rejects.toThrow('currency-missmatch')
})
it('Should deliver an "Item" event', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.View,
item: {
id: '111',
type: ItemType.Drug,
quantity: 2,
price: 100,
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
await ECommerce.logItemEvent(event, {})
expect(senderSpy).toBeCalledWith(
expect.stringMatching(device_id),
expect.stringMatching(device_id),
expect.objectContaining(
{
type: ECommerceTypes.Item,
props: { ...event}
}),
expect.anything())
})
it('Should not inject the drug when the action is not item_view', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.Detail,
item: {
id: '111',
quantity: 2,
type: ItemType.Drug,
price: 100,
currency: CurrencyCode.EUR,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
await ECommerce.logItemEvent(event, {})
expect(catalogSpy).not.toBeCalled()
})
it('Should inject the drug when the action is item_view', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.View,
item: {
id: '111',
quantity: 2,
type: ItemType.Drug,
price: 100,
currency: CurrencyCode.FKP,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
await ECommerce.logItemEvent(event, { name: 'drugX', supplier: 'supplierY' })
expect(catalogSpy).toBeCalled()
})
it('Should inject blood without meta', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectBlood')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.View,
item: {
id: '111',
quantity: 2,
type: ItemType.Blood,
price: 100,
currency: CurrencyCode.FKP,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
const catalogProperties = {
market_id: "",
blood_component: 'aa',
blood_group: 'bb',
packaging_size: 50,
packaging_units: 'units'
}
await expect(() => ECommerce.logItemEvent(event, catalogProperties)).not.toThrowError()
})
it('Should inject medicalEquipment data into catalog', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectMedicalEquipment')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.View,
item: {
id: '111',
quantity: 2,
type: ItemType.MedicalEquipment,
price: 100,
currency: CurrencyCode.FKP,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
const catalogProperties = {
name: 'medical equip. name', // medical consumable name (e.g. 2-Way Catheter 16FR)
market_id: '123',
supplier_id: '123',
supplier_name: '123-name',
packaging_size: 50,
packaging_units: 'units'
}
await ECommerce.logItemEvent(event, catalogProperties)
expect(catalogSpy).toBeCalled()
})
it('Should inject the drug when the action is item_view - itemId must be inserted automatically', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const itemId = "111"
const drugProperties = { name: 'drugX', supplier: 'supplierY' }
const event = {
action: ItemAction.View,
item: {
id: '111',
quantity: 2,
type: ItemType.Drug,
price: 100,
currency: CurrencyCode.FKP,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
await ECommerce.logItemEvent(event, drugProperties)
expect(catalogSpy).toBeCalledWith(itemId, drugProperties)
})
it('Should trigger an Exception when the item info is malformed', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const bsCore = CfCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id, false)
const event = {
action: ItemAction.View,
item: {
id: '111',
quantity: 2,
price: 100,
stock_status: StockStatus.InStock,
promo_id: 'asdf'
},
search_id: "1"
}
await expect(ECommerce.logItemEvent(event as any, {})).rejects.toThrowError()
})
it('Should ingest data into item catalog when an item is impressed in the screen', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')
class ImpressionsDetectorMock extends EventEmitter {
constructor() {
super()
}
start() {
setTimeout(() => {
console.log('timeout impression detector!')
const dataForCatalog = {
market_id: '2222',
name: 'drugname',
description: 'description',
supplier_id: 'supplier_id',
supplier_name: 'supplier_name',
packaging_size: 50,
packaging_units: 'units'
}
const data = {
dataset: {
logId: '1222',
logCurrency: 'USD',
logPrice: 123,
logQuantity: 2,
logStockStatus: 'in_stock',
logPromoId: '',
logDrugCatalogObject: JSON.stringify(dataForCatalog)
},
appData: {}
}
this.emit(ImpressionEventType.Impression, data)
}, 20)
}
}
const bsCore = CfCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id, false)
ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111')
await new Promise((resolve) => setTimeout(() => resolve(''), 100))
expect(catalogSpy).toBeCalled()
})
it('Should throw an exception when trying to restart impression with an unknown container classname', async () => {
const senderSpy = jest.spyOn(mockSender, 'add')
const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug')
class ImpressionsDetectorMock extends EventEmitter {
constructor() {
super()
}
start() { }
}
const bsCore = CfCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id, false)
ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111')
expect(() => ECommerce.restartTrackingImpressions('unknown-container', '111')).toThrowError()
})
})