Skip to content

Commit

Permalink
Better test coverage (#9)
Browse files Browse the repository at this point in the history
* generate coverage reports. refactor code for readability

* update comment

* add new test for falsy inp data

* fix typescript error with window

* fix typescript error with window
  • Loading branch information
ethangardner authored Dec 4, 2024
1 parent d07b817 commit 5bf9e17
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 98 deletions.
144 changes: 123 additions & 21 deletions src/format/format-event-data.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { it, describe, expect } from 'vitest';
import { formatEventData } from './format-event-data.js';
import { formatEventData, NOT_SET_MESSAGE } from './format-event-data.js';
import type {
CLSAttribution,
FCPAttribution,
Expand All @@ -9,7 +9,7 @@ import type {
} from 'web-vitals';

describe('formatEventData', () => {
it('should format CLS data correctly', () => {
it('should format CLS data', () => {
const attribution = {
largestShiftTime: 1000,
loadState: 'complete',
Expand All @@ -25,7 +25,23 @@ describe('formatEventData', () => {
});
});

it('should format FCP data correctly', () => {
it('should handle CLS attribution with no largestShiftTarget', () => {
const attribution = {
largestShiftTime: 1000,
loadState: 'complete',
largestShiftTarget: undefined,
} satisfies CLSAttribution;

const result = formatEventData('CLS', attribution);

expect(result).toEqual({
debug_time: attribution.largestShiftTime,
debug_load_state: attribution.loadState,
debug_target: NOT_SET_MESSAGE,
});
});

it('should format FCP data', () => {
const attribution = {
timeToFirstByte: 7.199999999254942,
firstByteToFCP: 456.9000000022352,
Expand All @@ -42,29 +58,26 @@ describe('formatEventData', () => {
});
});

it('should format LCP data correctly', () => {
it('should handle FCP attribution if loadState is not set', () => {
const attribution = {
url: 'https://localhost/',
timeToFirstByte: 100,
resourceLoadDelay: 50,
resourceLoadDuration: 120,
elementRenderDelay: 10,
element: 'body>div#main',
} satisfies LCPAttribution;
timeToFirstByte: 7.199999999254942,
firstByteToFCP: 456.9000000022352,
// @ts-expect-error - deviate from the type
loadState: '',
} satisfies FCPAttribution;

const result = formatEventData('LCP', attribution);
// @ts-expect-error - deviate from the type
const result = formatEventData('FCP', attribution);

expect(result).toEqual({
debug_url: attribution.url,
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_target: attribution.element,
debug_first_byte_to_fcp: attribution.firstByteToFCP,
debug_load_state: attribution.loadState,
debug_target: NOT_SET_MESSAGE,
});
});

it('should format INP data correctly', () => {
it('should format INP data', () => {
const attribution = {
interactionTarget: 'html>body',
interactionTargetElement: undefined,
Expand Down Expand Up @@ -92,7 +105,79 @@ describe('formatEventData', () => {
});
});

it('should format TTFB data correctly', () => {
it('should handle INP data if interactionTarget is not set', () => {
const attribution = {
interactionTarget: '',
interactionTargetElement: undefined,
interactionType: 'keyboard',
interactionTime: 235.5,
nextPaintTime: 435.5,
processedEventEntries: [],
longAnimationFrameEntries: [],
inputDelay: 0.5,
processingDuration: 93,
presentationDelay: 106.5,
loadState: 'dom-interactive',
} satisfies INPAttribution;

const result = formatEventData('INP', attribution);

expect(result).toEqual({
debug_event: attribution.interactionType,
debug_time: Math.round(attribution.interactionTime),
debug_load_state: attribution.loadState,
debug_target: NOT_SET_MESSAGE,
debug_interaction_delay: Math.round(attribution.inputDelay),
debug_processing_duration: Math.round(attribution.processingDuration),
debug_presentation_delay: Math.round(attribution.presentationDelay),
});
});

it('should format LCP data', () => {
const attribution = {
url: 'https://localhost/',
timeToFirstByte: 100,
resourceLoadDelay: 50,
resourceLoadDuration: 120,
elementRenderDelay: 10,
element: 'body>div#main',
} satisfies LCPAttribution;

const result = formatEventData('LCP', attribution);

expect(result).toEqual({
debug_url: attribution.url,
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_target: attribution.element,
});
});

it('should handle LCP data if the element is not set', () => {
const attribution = {
url: 'https://localhost/',
timeToFirstByte: 100,
resourceLoadDelay: 50,
resourceLoadDuration: 120,
elementRenderDelay: 10,
element: '',
} satisfies LCPAttribution;

const result = formatEventData('LCP', attribution);

expect(result).toEqual({
debug_url: attribution.url,
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_target: NOT_SET_MESSAGE,
});
});

it('should format TTFB data', () => {
const attribution = {
waitingDuration: 0,
cacheDuration: 0,
Expand All @@ -112,9 +197,26 @@ describe('formatEventData', () => {
});
});

it('should ignore the deprecated FID event', () => {
const attribution = {
waitingDuration: 0,
cacheDuration: 0,
dnsDuration: 0,
connectionDuration: 2015,
requestDuration: 47,
} as TTFBAttribution;

// @ts-expect-error - FID is deprecated
const result = formatEventData('FID', attribution);

expect(result).toEqual({
debug_target: NOT_SET_MESSAGE,
});
});

it('should return default/empty params if no attribution data is provided', () => {
// @ts-expect-error - unknown is not valid
const result = formatEventData('unknown', null);
// @ts-expect-error - null is not valid for arg2
const result = formatEventData('TTFB', null);

expect(result).toEqual({
debug_target: '(not set)',
Expand Down
129 changes: 60 additions & 69 deletions src/format/format-event-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,78 +18,69 @@ export type WebVitalsAttribution =
| LCPAttribution
| TTFBAttribution;

export const NOT_SET_MESSAGE = '(not set)';

const formatCLS = (attribution: CLSAttribution) => ({
debug_time: attribution.largestShiftTime,
debug_load_state: attribution.loadState,
debug_target: attribution.largestShiftTarget || NOT_SET_MESSAGE,
});

const formatFCP = (attribution: FCPAttribution) => ({
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_first_byte_to_fcp: attribution.firstByteToFCP,
debug_load_state: attribution.loadState,
debug_target: attribution.loadState || NOT_SET_MESSAGE,
});

const formatINP = (attribution: DAPINPAttribution) => ({
debug_event: attribution.interactionType,
debug_time: Math.round(attribution.interactionTime),
debug_load_state: attribution.loadState,
debug_target: attribution.interactionTarget || NOT_SET_MESSAGE,
debug_interaction_delay: Math.round(attribution.inputDelay),
debug_processing_duration: Math.round(attribution.processingDuration),
debug_presentation_delay: Math.round(attribution.presentationDelay),
...formatLongAnimationFrameData(attribution),
});

const formatLCP = (attribution: LCPAttribution) => ({
debug_url: attribution.url,
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_target: attribution.element || NOT_SET_MESSAGE,
});

const formatTTFB = (attribution: TTFBAttribution) => ({
debug_waiting_duration: attribution.waitingDuration,
debug_dns_duration: attribution.dnsDuration,
debug_connection_duration: attribution.connectionDuration,
debug_cache_duration: attribution.cacheDuration,
debug_request_duration: attribution.requestDuration,
});

export const formatEventData = (
name: WebVitalsName,
attribution: WebVitalsAttribution,
) => {
// In some cases there won't be any entries (e.g. if CLS is 0,
// or for LCP after a bfcache restore), so we have to check first.
if (attribution) {
if (name === 'CLS') {
return {
debug_time: (attribution as CLSAttribution).largestShiftTime,
debug_load_state: (attribution as CLSAttribution).loadState,
debug_target:
(attribution as CLSAttribution).largestShiftTarget || '(not set)',
};
}
if (name === 'FCP') {
return {
debug_time_to_first_byte: (attribution as FCPAttribution)
.timeToFirstByte,
debug_first_byte_to_fcp: (attribution as FCPAttribution).firstByteToFCP,
debug_load_state: (attribution as FCPAttribution).loadState,
debug_target: (attribution as FCPAttribution).loadState || '(not set)',
};
}
if (name === 'INP') {
return {
debug_event: (attribution as INPAttribution).interactionType,
debug_time: Math.round((attribution as INPAttribution).interactionTime),
debug_load_state: (attribution as INPAttribution).loadState,
debug_target:
(attribution as INPAttribution).interactionTarget || '(not set)',
debug_interaction_delay: Math.round(
(attribution as INPAttribution).inputDelay,
),
debug_processing_duration: Math.round(
(attribution as INPAttribution).processingDuration,
),
debug_presentation_delay: Math.round(
(attribution as INPAttribution).presentationDelay,
),
...formatLongAnimationFrameData(attribution as DAPINPAttribution),
};
}
if (name === 'LCP') {
return {
debug_url: (attribution as LCPAttribution).url,
debug_time_to_first_byte: (attribution as LCPAttribution)
.timeToFirstByte,
debug_resource_load_delay: (attribution as LCPAttribution)
.resourceLoadDelay,
debug_resource_load_duration: (attribution as LCPAttribution)
.resourceLoadDuration,
debug_element_render_delay: (attribution as LCPAttribution)
.elementRenderDelay,
debug_target: (attribution as LCPAttribution).element || '(not set)',
};
}
if (name === 'TTFB') {
return {
debug_waiting_duration: (attribution as TTFBAttribution)
.waitingDuration,
debug_dns_duration: (attribution as TTFBAttribution).dnsDuration,
debug_connection_duration: (attribution as TTFBAttribution)
.connectionDuration,
debug_cache_duration: (attribution as TTFBAttribution).cacheDuration,
debug_request_duration: (attribution as TTFBAttribution)
.requestDuration,
};
}
if (!attribution) {
return { debug_target: NOT_SET_MESSAGE };
}

switch (name) {
case 'CLS':
return formatCLS(attribution as CLSAttribution);
case 'FCP':
return formatFCP(attribution as FCPAttribution);
case 'INP':
return formatINP(attribution as DAPINPAttribution);
case 'LCP':
return formatLCP(attribution as LCPAttribution);
case 'TTFB':
return formatTTFB(attribution as TTFBAttribution);
default:
return { debug_target: NOT_SET_MESSAGE };
}
// Return default/empty params in case there is no attribution.
return {
debug_target: '(not set)',
};
};
45 changes: 45 additions & 0 deletions src/format/format-long-animation-frame-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,51 @@ describe('formatLongAnimationFrameData', () => {

expect(result).toEqual({});
});

it('should format INP data correctly if there is falsy data', () => {
const attribution = {
longAnimationFrameEntries: [
{
name: 'long-animation-frame',
entryType: 'long-animation-frame',
startTime: 5168847.600000024,
duration: 90,
renderStart: '',
styleAndLayoutStart: '',
firstUIEventTimestamp: 5168847,
blockingDuration: 38,
scripts: [
{
name: 'script',
entryType: 'script',
startTime: 5168847.899999976,
duration: 85,
invoker: 'BUTTON#thrash-layout.onclick',
invokerType: 'event-listener',
windowAttribution: 'self',
executionStart: 5168847.899999976,
forcedStyleAndLayoutDuration: 80,
pauseDuration: 0,
sourceURL: 'http://localhost:8000/demo/',
sourceFunctionName: '',
sourceCharPosition: 72,
},
],
},
],
};

const {
debug_loaf_entry_render_duration,
debug_loaf_entry_style_and_layout_duration,
debug_loaf_entry_work_duration,
// @ts-expect-error - the object is pared down for testing. It won't be compliant with all props for the type
} = formatLongAnimationFrameData(attribution);

expect(debug_loaf_entry_render_duration).toEqual(0);
expect(debug_loaf_entry_style_and_layout_duration).toEqual(0);
expect(debug_loaf_entry_work_duration).toEqual(90);
});
});

/**
Expand Down
Loading

0 comments on commit 5bf9e17

Please sign in to comment.