Skip to content

Commit

Permalink
test: add unit tests for lcp image resource
Browse files Browse the repository at this point in the history
  • Loading branch information
williazz committed Sep 14, 2023
1 parent 24b9ddc commit d46df1a
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/event-bus/EventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class EventBus<T = Topic> {

subscribe(topic: T, subscriber: Subscriber): void {
const list = this.subscribers.get(topic) ?? [];
if (list.length === 0) {
if (!list.length) {
this.subscribers.set(topic, list);
}
list.push(subscriber);
Expand Down
37 changes: 36 additions & 1 deletion src/event-bus/__tests__/EventBus.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import EventBus from '../EventBus';
import { context } from '../../test-utils/test-utils';
import EventBus, { Topic } from '../EventBus';
import { InternalPlugin } from '../../plugins/InternalPlugin';
import { PluginContext } from '../../plugins/types';

export enum MockTopics {
FOOD = 'food',
BOOKS = 'books'
}

const MockPluginId = 'Mock-Plugin';
class MockPlugin extends InternalPlugin {
count = 0;

constructor() {
super(MockPluginId);
this.subscriber = this.subscriber.bind(this);
}

enable(): void {} // eslint-disable-line
disable(): void {} // eslint-disable-line

protected onload(): void {
this.context.eventBus.subscribe(Topic.EVENT, this.subscriber); // eslint-disable-line
}

subscriber(msg: any) {
this.count++;
}
}

describe('EventBus tests', () => {
let eventBus: EventBus<MockTopics>;
const l1 = jest.fn();
Expand Down Expand Up @@ -50,4 +75,14 @@ describe('EventBus tests', () => {
// assert
expect(l2).not.toHaveBeenCalled();
});

test('when plugin subscribes then observes events', async () => {
const plugin = new MockPlugin();
const spy = jest.spyOn(plugin, 'subscriber');

plugin.load(context);
context.eventBus.dispatch(Topic.EVENT, 'hat');

expect(spy).toHaveBeenCalledWith('hat');
});
});
26 changes: 15 additions & 11 deletions src/plugins/event-plugins/WebVitalsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class WebVitalsPlugin extends InternalPlugin {
this.lcpHelper = this.lcpHelper.bind(this);
}
private resourceEventIds = new Map<string, string>();
private navigationEventsIds: string[] = [];
private navigationEventId?: string;

// eslint-disable-next-line @typescript-eslint/no-empty-function
enable(): void {}
Expand All @@ -47,13 +47,13 @@ export class WebVitalsPlugin extends InternalPlugin {
configure(config: any): void {}

protected onload(): void {
this.context?.eventBus.subscribe(Topic.EVENT, this.lcpHelper); // eslint-disable-line
this.context.eventBus.subscribe(Topic.EVENT, this.lcpHelper); // eslint-disable-line
onLCP((metric) => this.handleLCP(metric));
onFID((metric) => this.handleFID(metric));
onCLS((metric) => this.handleCLS(metric));
}

lcpHelper(msg: any) {
private lcpHelper(msg: any) {
const event = msg as ParsedRumEvent;
switch (event.type) {
// lcp resource is either image or text
Expand All @@ -63,17 +63,21 @@ export class WebVitalsPlugin extends InternalPlugin {
details.fileType === ResourceType.IMAGE ||
details.initiatorType === 'img'
) {
const key = performanceKey(event.details as HasLatency);
this.resourceEventIds.set(key, event.id);
this.resourceEventIds.set(
performanceKey(event.details as HasLatency),
event.id
);
}
break;
case PERFORMANCE_NAVIGATION_EVENT_TYPE:
this.navigationEventsIds.push(event.id);
if (!this.navigationEventId) {
this.navigationEventId = event.id;
}
break;
}
}

handleLCP(metric: LCPMetricWithAttribution | Metric) {
private handleLCP(metric: LCPMetricWithAttribution | Metric) {
const a = (metric as LCPMetricWithAttribution).attribution;
const attribution: any = {
element: a.element,
Expand All @@ -82,7 +86,7 @@ export class WebVitalsPlugin extends InternalPlugin {
resourceLoadDelay: a.resourceLoadDelay,
resourceLoadTime: a.resourceLoadTime,
elementRenderDelay: a.elementRenderDelay,
navigationEntry: this.navigationEventsIds[0]
navigationEntry: this.navigationEventId
};
if (a.lcpResourceEntry) {
const key = performanceKey(a.lcpResourceEntry as HasLatency);
Expand All @@ -97,10 +101,10 @@ export class WebVitalsPlugin extends InternalPlugin {
// teardown
this.context?.eventBus.unsubscribe(Topic.EVENT, this.lcpHelper); // eslint-disable-line
this.resourceEventIds.clear();
this.navigationEventsIds = [];
this.navigationEventId = undefined;
}

handleCLS(metric: CLSMetricWithAttribution | Metric) {
private handleCLS(metric: CLSMetricWithAttribution | Metric) {
const a = (metric as CLSMetricWithAttribution).attribution;
this.context?.record(CLS_EVENT_TYPE, {
version: '1.0.0',
Expand All @@ -114,7 +118,7 @@ export class WebVitalsPlugin extends InternalPlugin {
} as CumulativeLayoutShiftEvent);
}

handleFID(metric: FIDMetricWithAttribution | Metric) {
private handleFID(metric: FIDMetricWithAttribution | Metric) {
const a = (metric as FIDMetricWithAttribution).attribution;
this.context?.record(FID_EVENT_TYPE, {
version: '1.0.0',
Expand Down
1 change: 1 addition & 0 deletions src/plugins/event-plugins/__tests__/ResourcePlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('ResourcePlugin tests', () => {
);
expect(record.mock.calls[0][1]).toEqual(
expect.objectContaining({
startTime: resourceEvent.startTime,
fileType: resourceEvent.fileType,
duration: resourceEvent.duration,
transferSize: resourceEvent.transferSize,
Expand Down
120 changes: 111 additions & 9 deletions src/plugins/event-plugins/__tests__/WebVitalsPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { WebVitalsPlugin } from '../WebVitalsPlugin';
import { context, record } from '../../../test-utils/test-utils';
import { ResourceType } from '../../../utils/common-utils';
import {
CLS_EVENT_TYPE,
FID_EVENT_TYPE,
LCP_EVENT_TYPE
} from '../../utils/constant';
LCP_EVENT_TYPE,
PERFORMANCE_RESOURCE_EVENT_TYPE
} from '../../../plugins/utils/constant';
import { context, record } from '../../../test-utils/test-utils';
import { Topic } from '../../../event-bus/EventBus';
import { WebVitalsPlugin } from '../WebVitalsPlugin';

const mockLCPData = {
delta: 239.51,
Expand Down Expand Up @@ -47,11 +50,35 @@ const mockCLSData = {
}
};

const imagePerformanceEntry = {
duration: 50,
startTime: 100
};

const imageResourceRumEvent: any = {
id: 'img-id',
type: PERFORMANCE_RESOURCE_EVENT_TYPE,
details: {
fileType: ResourceType.IMAGE,
...imagePerformanceEntry
}
};

const mockLCPDataWithImage = Object.assign({}, mockLCPData, {
attribution: {
...mockLCPData.attribution,
lcpResourceEntry: imagePerformanceEntry
},
onFID: jest.fn(),
onCLS: jest.fn()
});

jest.mock('web-vitals/attribution', () => {
return {
onLCP: jest
.fn()
.mockImplementation((callback) => callback(mockLCPData)),
onLCP: jest.fn().mockImplementation((callback) => {
context.eventBus.dispatch(Topic.EVENT, imageResourceRumEvent);
callback(mockLCPDataWithImage);
}),
onFID: jest
.fn()
.mockImplementation((callback) => callback(mockFIDData)),
Expand Down Expand Up @@ -80,7 +107,7 @@ describe('WebVitalsPlugin tests', () => {
expect.objectContaining({
version: '1.0.0',
value: mockLCPData.value,
attribution: {
attribution: expect.objectContaining({
element: mockLCPData.attribution.element,
url: mockLCPData.attribution.url,
timeToFirstByte: mockLCPData.attribution.timeToFirstByte,
Expand All @@ -89,7 +116,7 @@ describe('WebVitalsPlugin tests', () => {
resourceLoadTime: mockLCPData.attribution.resourceLoadTime,
elementRenderDelay:
mockLCPData.attribution.elementRenderDelay
}
})
})
);
});
Expand Down Expand Up @@ -170,4 +197,79 @@ describe('WebVitalsPlugin tests', () => {
// Assert
expect(record).toHaveBeenCalled();
});

test('when lcp image resource has filetype=image then eventId is attributed to lcp', async () => {
const plugin = new WebVitalsPlugin();

plugin.load(context);
expect(record).toHaveBeenCalledWith(
LCP_EVENT_TYPE,
expect.objectContaining({
attribution: expect.objectContaining({
lcpResourceEntry: imageResourceRumEvent.id
})
})
);
});

test('when lcp image resource has initiatorType=img then eventId is attributed to lcp', async () => {
// init
const fileType = imageResourceRumEvent.details.fileType;
delete imageResourceRumEvent.details.fileType;
imageResourceRumEvent.details.initiatorType = 'img';
const plugin = new WebVitalsPlugin();

// run
plugin.load(context);

// assert
expect(record).toHaveBeenCalledWith(
LCP_EVENT_TYPE,
expect.objectContaining({
attribution: expect.objectContaining({
lcpResourceEntry: imageResourceRumEvent.id
})
})
);

// restore
delete imageResourceRumEvent.details.initiatorType;
imageResourceRumEvent.details.fileType = fileType;
});

test('when no matching image resource does not exist then it is not attributed to lcp', async () => {
// init
const fileType = imageResourceRumEvent.details.fileType;
delete imageResourceRumEvent.details.fileType;
const plugin = new WebVitalsPlugin();

// run
plugin.load(context);

// assert
expect(record).toHaveBeenCalledWith(
LCP_EVENT_TYPE,
expect.objectContaining({
attribution: expect.not.objectContaining({
lcpResourceEntry: expect.anything()
})
})
);

// restore
imageResourceRumEvent.details.fileType = fileType;
});
test('when lcp is recorded then unsubscribe is called', async () => {
// init
const unsubscribeSpy = jest.spyOn(context.eventBus, 'unsubscribe');
const plugin = new WebVitalsPlugin();
const recordSpy = jest.spyOn(context, 'record');

// run
plugin.load(context);

// assert
expect(recordSpy).toHaveBeenCalled();
expect(unsubscribeSpy).toHaveBeenCalled();
});
});
2 changes: 1 addition & 1 deletion src/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../dispatch/dataplane';
import { ReadableStream } from 'web-streams-polyfill';
import EventBus from '../event-bus/EventBus';
jest.mock('../event-bus/EventBus');
// jest.mock('../event-bus/EventBus');

export const AWS_RUM_ENDPOINT = new URL(
'https://rumservicelambda.us-west-2.amazonaws.com'
Expand Down

0 comments on commit d46df1a

Please sign in to comment.