@zendesk/react-measure-timing-hooks
Version:
react hooks for measuring time to interactive and time to render of components
638 lines (588 loc) • 18.5 kB
text/typescript
import { describe, expect, it } from 'vitest'
import * as matchSpan from './matchSpan'
import type { SpanAnnotation } from './spanAnnotationTypes'
import type {
ActiveTraceInput,
ComponentRenderSpan,
SpanBase,
} from './spanTypes'
import type {
TicketIdRelationSchemasFixture,
UserIdRelationSchemasFixture,
} from './testUtility/fixtures/relationSchemas'
import type {
CompleteTraceDefinition,
DraftTraceContext,
MapSchemaToTypes,
} from './types'
const mockRelations: MapSchemaToTypes<
TicketIdRelationSchemasFixture['ticket']
> = {
ticketId: '123',
}
const mockEntryBase = {
type: 'element',
name: 'testEntry',
startTime: {
now: Date.now(),
epoch: Date.now(),
},
relatedTo: mockRelations,
attributes: {
attr1: 'value1',
attr2: 2,
},
duration: 100,
status: 'ok',
} as const satisfies SpanBase<TicketIdRelationSchemasFixture>
const mockPerformanceEntry = {
...mockEntryBase,
performanceEntry: {
entryType: 'element',
name: 'testEntry',
startTime: 0,
duration: 0,
toJSON: () => ({}),
},
} as const satisfies SpanBase<TicketIdRelationSchemasFixture>
// Mock data for ComponentRenderTraceEntry
const mockComponentEntry: ComponentRenderSpan<TicketIdRelationSchemasFixture> =
{
...mockEntryBase,
type: 'component-render',
errorInfo: undefined,
isIdle: true,
renderedOutput: 'content',
renderCount: 1,
}
const mockAnnotation: SpanAnnotation = {
id: '',
occurrence: 1,
operationRelativeEndTime: 0,
operationRelativeStartTime: 0,
recordedInState: 'active',
labels: [],
}
const mockContext = {
input: {
id: '123',
relatedTo: mockRelations,
startTime: {
now: Date.now(),
epoch: Date.now(),
},
variant: 'origin',
},
definition: {
name: 'test',
type: 'operation',
relationSchemaName: 'ticket',
relationSchema: { ticketId: String },
requiredSpans: [() => true],
computedSpanDefinitions: {},
computedValueDefinitions: {},
variants: {
origin: { timeout: 10_000 },
},
},
recordedItemsByLabel: {},
recordedItems: new Set(),
} as const satisfies DraftTraceContext<
'ticket',
TicketIdRelationSchemasFixture,
'origin'
>
type MockOrigin = (typeof mockContext)['input']['variant']
// TESTING TODO: ask chatgpt to add 'or' and 'not' tests
describe('matchSpan', () => {
describe('name', () => {
it('should return true for a matching entry based on name', () => {
const matcher = matchSpan.withName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('testEntry')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return true for function matchers for name', () => {
const matcher = matchSpan.withName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>((n: string) => n.startsWith('test'))
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return true for regex matchers for name', () => {
const matcher = matchSpan.withName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(/^test/)
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for a non-matching span based on name', () => {
const matcher = matchSpan.withName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('nonMatchingEntry')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('performanceEntryName', () => {
it('should return true for a matching span based on performanceEntryName', () => {
const matcher = matchSpan.withPerformanceEntryName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('testEntry')
const mockSpanAndAnnotation = {
span: mockPerformanceEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for a non-matching performanceEntryName', () => {
const matcher = matchSpan.withPerformanceEntryName<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('nonMatchingEntry')
const mockSpanAndAnnotation = {
span: mockPerformanceEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('type', () => {
describe('for Native Performance Entry', () => {
it('should return true for matching attributes', () => {
const matcher = matchSpan.withType<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('element')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for non-matching attributes', () => {
const matcher = matchSpan.withType<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('component-render')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('for ComponentRenderTraceEntry', () => {
it('should return true for a matching ComponentRenderTraceEntry', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withType('component-render'),
matchSpan.withName('testEntry'),
)
const mockSpanAndAnnotation = {
span: mockComponentEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for a non-matching ComponentRenderTraceEntry', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withType('component-render'),
matchSpan.withName('nonMatchingEntry'),
)
const mockSpanAndAnnotation = {
span: mockComponentEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
})
describe('status', () => {
it('should return true when status does match', () => {
const matcher = matchSpan.withStatus<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('ok')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false when status does not match', () => {
const matcher = matchSpan.withStatus<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>('error')
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('occurrence', () => {
it('should return true for occurrence matching', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(matchSpan.withName('testEntry'), matchSpan.withOccurrence(1))
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for non-matching occurrence', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(matchSpan.withName('testEntry'), matchSpan.withOccurrence(2))
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('attributes', () => {
it('should return true for matching attributes', () => {
const matcher = matchSpan.withAttributes<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
attr1: 'value1',
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for non-matching attributes', () => {
const matcher = matchSpan.withAttributes<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
attr1: 'wrongValue',
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('matchingRelation', () => {
it('should return true when relatedTo does match', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withName('testEntry'),
matchSpan.withMatchingRelations(['ticketId']),
)
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false when relatedTo does not match', () => {
const matcher = matchSpan.withAllConditions<
'ticket' | 'user',
TicketIdRelationSchemasFixture & UserIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withName('testEntry'),
matchSpan.withMatchingRelations(['ticketId', 'userId']),
)
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(
matcher(mockSpanAndAnnotation, {
...mockContext,
input: {
...mockContext.input,
relatedTo: {
ticketId: '123',
userId: '123',
},
},
}),
).toBe(false)
})
})
describe('isIdle', () => {
const mockMatcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(matchSpan.withName('testEntry'), matchSpan.whenIdle(true))
it('should return true for isIdle matching', () => {
const mockSpanAndAnnotation = {
span: mockComponentEntry,
annotation: mockAnnotation,
}
expect(mockMatcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false for non-matching isIdle', () => {
const mockEntry = { ...mockComponentEntry, isIdle: false }
const mockSpanAndAnnotation = {
span: mockEntry,
annotation: mockAnnotation,
}
expect(mockMatcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('combination of conditions', () => {
it('should return true when all conditions match', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withName('testEntry'),
matchSpan.withPerformanceEntryName('testEntry'),
matchSpan.withAttributes({ attr1: 'value1' }),
matchSpan.withMatchingRelations(['ticketId']),
matchSpan.withStatus('ok'),
matchSpan.withType('element'),
)
const mockSpanAndAnnotation = {
span: mockPerformanceEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false when all conditions match but name', () => {
const matcher = matchSpan.withAllConditions<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>(
matchSpan.withName('testEntries'), // does not match
matchSpan.withPerformanceEntryName('testEntry'),
matchSpan.withAttributes({ attr1: 'value1' }),
matchSpan.withMatchingRelations(['ticketId']),
matchSpan.withStatus('ok'),
matchSpan.withType('element'),
)
const mockSpanAndAnnotation = {
span: mockPerformanceEntry,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('fn', () => {
it('should return true when the custom function matches', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
fn: ({ span }) => span.name.startsWith('test'),
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false when the custom function does not match', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
fn: ({ span }) => span.name === 'nonMatchingEntry',
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
})
describe('fromDefinition', () => {
it('should correctly handle standard definition objects', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
name: 'testEntry',
type: 'element',
attributes: { attr1: 'value1' },
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
describe('oneOf', () => {
it('should return true when one of the conditions matches', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
oneOf: [
{ name: 'nonMatchingEntry' },
{ name: 'testEntry' },
{ name: 'anotherNonMatchingEntry' },
],
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should return false when none of the conditions match', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
oneOf: [
{ name: 'nonMatchingEntry1' },
{ name: 'nonMatchingEntry2' },
{ name: 'nonMatchingEntry3' },
],
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(false)
})
it('should handle complex conditions within oneOf', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
oneOf: [
{
name: 'nonMatchingEntry',
type: 'element',
},
{
name: 'testEntry',
type: 'component-render', // This doesn't match our entry
},
{
name: 'testEntry',
type: 'element',
attributes: { attr1: 'value1' },
},
],
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should carry over tags from sub-matchers', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
oneOf: [
{ name: 'nonMatchingEntry' },
{ name: 'testEntry', isIdle: true },
],
})
expect('idleCheck' in matcher).toBe(true)
expect(matcher.idleCheck).toBe(true)
})
it('should work with nested fromDefinition calls', () => {
// First, create a matcher using fromDefinition
const nestedMatcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
type: 'element',
})
// Then use that matcher along with another in withOneOfConditions
const combinedMatcher = matchSpan.withOneOfConditions(
nestedMatcher,
matchSpan.withName('nonMatchingEntry'),
)
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(combinedMatcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
it('should handle nested oneOf conditions', () => {
const matcher = matchSpan.fromDefinition<
'ticket',
TicketIdRelationSchemasFixture,
MockOrigin
>({
oneOf: [
{ name: 'nonMatchingEntry1' },
{
oneOf: [{ name: 'nonMatchingEntry2' }, { name: 'testEntry' }],
},
],
})
const mockSpanAndAnnotation = {
span: mockEntryBase,
annotation: mockAnnotation,
}
expect(matcher(mockSpanAndAnnotation, mockContext)).toBe(true)
})
})
})
})