Skip to content

Commit b3ff042

Browse files
committed
add tests for custom events
1 parent c492a2f commit b3ff042

10 files changed

Lines changed: 98 additions & 11 deletions

File tree

__test__/support/helpers/requests.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,16 @@ export const setUpdateUserError = ({
325325
status,
326326
retryAfter,
327327
});
328+
329+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330+
// custom events
331+
const getCustomEventsUri = () =>
332+
`**/apps/${APP_ID}/integrations/sdk/custom_events`;
333+
export const sendCustomEventFn = vi.fn();
334+
export const setSendCustomEventResponse = () =>
335+
getHandler({
336+
uri: getCustomEventsUri(),
337+
method: 'post',
338+
status: 200,
339+
callback: sendCustomEventFn,
340+
});

src/core/CoreModule.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { logMethodCall } from 'src/shared/utils/utils';
22
import { CustomEventController } from './controllers/CustomEventController';
3+
import { CustomEventsOperationExecutor } from './executors/CustomEventOperationExecutor';
34
import { IdentityOperationExecutor } from './executors/IdentityOperationExecutor';
45
import { LoginUserOperationExecutor } from './executors/LoginUserOperationExecutor';
56
import { RefreshUserOperationExecutor } from './executors/RefreshUserOperationExecutor';
@@ -119,13 +120,15 @@ export default class CoreModule {
119120
this.rebuildUserService,
120121
this.newRecordsState,
121122
);
123+
const customEventOpExecutor = new CustomEventsOperationExecutor();
122124

123125
return [
124126
identityOpExecutor,
125127
loginOpExecutor,
126128
refreshOpExecutor,
127129
subscriptionOpExecutor,
128130
updateSubOpExecutor,
131+
customEventOpExecutor,
129132
];
130133
}
131134
}

src/core/controllers/CustomEventController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class CustomEventController implements ICustomEventController {
2323
appId,
2424
onesignalId: identityModel.onesignalId,
2525
externalId: identityModel.externalId,
26-
timestamp: Date.now(),
26+
timestamp: new Date().toISOString(),
2727
event,
2828
});
2929

src/core/executors/CustomEventOperationExecutor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class CustomEventsOperationExecutor implements IOperationExecutor {
5454
external_id: operation.externalId,
5555
timestamp: operation.timestamp,
5656
payload: {
57-
...operation.event.properties,
57+
...(operation.event.properties ?? {}),
5858
os_sdk: this.eventMetadata,
5959
},
6060
},

src/core/requestService/RequestService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ export class RequestService {
276276
const { appId } = requestMetadata;
277277
return OneSignalApiBase.post(
278278
`apps/${appId}/integrations/sdk/custom_events`,
279-
[event],
279+
{
280+
events: [event],
281+
},
280282
requestMetadata.jwtHeader,
281283
);
282284
}

src/core/types/customEvents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface ICustomEvent {
22
name: string;
3-
properties: Record<string, unknown>;
3+
properties?: Record<string, unknown>;
44
}
55

66
export interface ICustomEventController {

src/onesignal/OneSignal.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import {
1919
getUserFn,
2020
mockPageStylesCss,
2121
mockServerConfig,
22+
sendCustomEventFn,
2223
setAddAliasError,
2324
setAddAliasResponse,
2425
setCreateSubscriptionResponse,
2526
setCreateUserResponse,
2627
setDeleteAliasResponse,
2728
setDeleteSubscriptionResponse,
2829
setGetUserResponse,
30+
setSendCustomEventResponse,
2931
setTransferSubscriptionResponse,
3032
setUpdateUserResponse,
3133
transferSubscriptionFn,
@@ -935,6 +937,39 @@ describe('OneSignal', () => {
935937
});
936938
});
937939

940+
describe('Custom Events', () => {
941+
test('can send a custom event', async () => {
942+
setSendCustomEventResponse();
943+
944+
const name = 'test_event';
945+
const properties = {
946+
test_property: 'test_value',
947+
};
948+
949+
window.OneSignal.User.trackEvent(name, properties);
950+
951+
await vi.waitUntil(() => sendCustomEventFn.mock.calls.length === 1);
952+
953+
expect(sendCustomEventFn).toHaveBeenCalledWith({
954+
events: [
955+
{
956+
name,
957+
onesignal_id: DUMMY_ONESIGNAL_ID,
958+
payload: {
959+
os_sdk: {
960+
sdk: '1',
961+
device_model: '',
962+
device_os: 56,
963+
},
964+
test_property: 'test_value',
965+
},
966+
timestamp: expect.any(String),
967+
},
968+
],
969+
});
970+
});
971+
});
972+
938973
test('should preserve operations order without needing await', async () => {
939974
await setupSubModelStore({
940975
id: DUMMY_SUBSCRIPTION_ID,

src/onesignal/User.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import {
1212
} from '../shared/errors/InvalidArgumentError';
1313
import {
1414
isObject,
15+
isObjectSerializable,
1516
isValidEmail,
16-
isValidJsonObject,
1717
logMethodCall,
1818
} from '../shared/utils/utils';
1919

@@ -282,17 +282,17 @@ export default class User {
282282
return OneSignal.coreDirector.getPropertiesModel().language;
283283
}
284284

285-
public trackEvent(name: string, properties?: Record<string, unknown>) {
286-
if (!isValidJsonObject(properties)) {
285+
public trackEvent(name: string, properties: Record<string, unknown> = {}) {
286+
if (!isObjectSerializable(properties)) {
287287
return Log.error(
288288
'Custom event properties must be a JSON-serializable object',
289289
);
290290
}
291291

292292
logMethodCall('trackEvent', { name, properties });
293-
OneSignal.coreDirector.customEventController.sendCustomEvent(
293+
OneSignal.coreDirector.customEventController.sendCustomEvent({
294294
name,
295295
properties,
296-
);
296+
});
297297
}
298298
}

src/onesignal/UserNamespace.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import {
22
DUMMY_ONESIGNAL_ID,
33
DUMMY_PUSH_TOKEN,
44
} from '__test__/support/constants';
5+
import Log from 'src/shared/libraries/Log';
56
import { IDManager } from 'src/shared/managers/IDManager';
67
import { TestEnvironment } from '../../__test__/support/environment/TestEnvironment';
78
import UserChangeEvent from '../page/models/UserChangeEvent';
89
import { Subscription } from '../shared/models/Subscription';
10+
import User from './User';
911
import UserNamespace from './UserNamespace';
1012

11-
vi.mock('../shared/libraries/Log');
13+
const errorSpy = vi.spyOn(Log, 'error').mockImplementation(() => '');
1214

1315
describe('UserNamespace', () => {
1416
let userNamespace: UserNamespace;
@@ -427,4 +429,32 @@ describe('UserNamespace', () => {
427429
expect(customNamespace.PushSubscription).toBeDefined();
428430
});
429431
});
432+
433+
describe('Custom Events', () => {
434+
test('should call track event', () => {
435+
const trackEventSpy = vi.spyOn(User.prototype, 'trackEvent');
436+
const name = 'test_event';
437+
const properties = {
438+
test_property: 'test_value',
439+
};
440+
441+
// should validate properties
442+
// @ts-expect-error - mock invalid argument
443+
userNamespace.trackEvent(name, 123);
444+
expect(errorSpy).toHaveBeenCalledWith(
445+
'Custom event properties must be a JSON-serializable object',
446+
);
447+
448+
// big ints can't be serialized
449+
userNamespace.trackEvent(name, {
450+
data: 10n,
451+
});
452+
expect(errorSpy).toHaveBeenCalledWith(
453+
'Custom event properties must be a JSON-serializable object',
454+
);
455+
456+
userNamespace.trackEvent(name, properties);
457+
expect(trackEventSpy).toHaveBeenCalledWith(name, properties);
458+
});
459+
});
430460
});

src/shared/utils/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,11 @@ export function isObject(value: unknown) {
332332
return typeof value === 'object' && value !== null && !Array.isArray(value);
333333
}
334334

335-
export function isValidJsonObject(value: unknown): boolean {
335+
/**
336+
* Returns true if the value is a JSON-serializable object.
337+
*/
338+
export function isObjectSerializable(value: unknown): boolean {
339+
if (!isObject(value)) return false;
336340
try {
337341
JSON.stringify(value);
338342
return true;

0 commit comments

Comments
 (0)