UNPKG

@zendesk/react-measure-timing-hooks

Version:

react hooks for measuring time to interactive and time to render of components

390 lines 19.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); require("./testUtility/asciiTimelineSerializer"); const vitest_1 = require("vitest"); const makeTimeline_1 = require("./testUtility/makeTimeline"); const processSpans_1 = require("./testUtility/processSpans"); const TraceManager_1 = require("./TraceManager"); (0, vitest_1.describe)('Tracer', () => { let reportFn; // TS doesn't like that reportFn is wrapped in Mock<> type const getReportFn = () => reportFn; let generateId; let reportErrorFn; (0, vitest_1.beforeEach)(() => { reportFn = vitest_1.vitest.fn(); generateId = vitest_1.vitest.fn().mockReturnValue('trace-id'); reportErrorFn = vitest_1.vitest.fn(); vitest_1.vitest.useFakeTimers({ now: 0 }); }); (0, vitest_1.describe)('variants', () => { (0, vitest_1.it)('uses additional required spans from variant', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'test.operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'base-required' }], variants: { variant_a: { timeout: 1_000, additionalRequiredSpans: [{ name: 'extra-required' }], }, variant_b: { timeout: 1_000, }, }, }); // Start trace with variant_a - should require both spans tracer.start({ relatedTo: { id: '1' }, variant: 'variant_a', }); // Only see base-required span - should not complete // prettier-ignore const { spans: firstSpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('base-required', 0)}-----${makeTimeline_1.Check} Time: ${0} ${50} ${100} `; (0, processSpans_1.processSpans)(firstSpans, traceManager); (0, vitest_1.expect)(reportFn).not.toHaveBeenCalled(); // See both required spans - should complete // prettier-ignore const { spans: secondSpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required', 0)} Time: ${150} ${200} `; (0, processSpans_1.processSpans)(secondSpans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.status).toBe('ok'); (0, vitest_1.expect)(report.duration).toBe(200); }); (0, vitest_1.it)('uses additional interrupt on spans from variant', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'test.operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'required' }], debounceWindow: 100, variants: { variant_a: { timeout: 1_000, additionalInterruptOnSpans: [{ name: 'variant-a-interrupt' }], }, }, }); tracer.start({ relatedTo: { id: '1' }, variant: 'variant_a', }); // prettier-ignore const { spans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('span', 0)}------${(0, makeTimeline_1.Render)('variant-a-interrupt', 0)}---${(0, makeTimeline_1.Render)('required', 0)} Time: ${0} ${100} ${250} `; (0, processSpans_1.processSpans)(spans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.status).toBe('interrupted'); (0, vitest_1.expect)(report.duration).toBe(null); }); (0, vitest_1.it)('uses additional debounce spans from variant', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'test.operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'required' }], debounceOnSpans: [{ name: 'base-debounce' }], debounceWindow: 100, variants: { variant_a: { timeout: 1_000, additionalDebounceOnSpans: [{ name: 'extra-debounce' }], }, }, }); tracer.start({ relatedTo: { id: '1' }, variant: 'variant_a', }); // prettier-ignore const { spans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('required', 0)}---${(0, makeTimeline_1.Render)('base-debounce', 0)}---${(0, makeTimeline_1.Render)('extra-debounce', 0)}---${makeTimeline_1.Check} Time: ${0} ${50} ${100} ${250} `; (0, processSpans_1.processSpans)(spans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.status).toBe('ok'); (0, vitest_1.expect)(report.duration).toBe(100); }); (0, vitest_1.it)('different variants can have different additional spans', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'test.operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'base-required' }], variants: { variant_a: { timeout: 1_000, additionalRequiredSpans: [{ name: 'extra-required-a' }], }, variant_b: { timeout: 1_000, additionalRequiredSpans: [{ name: 'extra-required-b' }], }, }, }); // Start trace with variant_a tracer.start({ relatedTo: { id: '1' }, variant: 'variant_a', }); // Complete variant_a requirements // prettier-ignore const { spans: variantASpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required-a', 0)} Time: ${0} ${50} `; (0, processSpans_1.processSpans)(variantASpans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); (0, vitest_1.expect)(reportFn.mock.calls[0][0].status).toBe('ok'); reportFn.mockClear(); // Start new trace with variant_b tracer.start({ relatedTo: { id: '1' }, variant: 'variant_b', }); // Complete variant_b requirements // prettier-ignore const { spans: variantBSpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('base-required', 0)}-----${(0, makeTimeline_1.Render)('extra-required-b', 0)} Time: ${100} ${150} `; (0, processSpans_1.processSpans)(variantBSpans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); (0, vitest_1.expect)(reportFn.mock.calls[0][0].status).toBe('ok'); }); }); (0, vitest_1.describe)('addRequiredSpansToCurrentTrace', () => { (0, vitest_1.it)('adds required spans to an existing trace', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'test.operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'hello' }, { name: 'initial-required' }], variants: { default: { timeout: 1_000 }, }, }); // Start trace tracer.start({ relatedTo: { id: '1' }, variant: 'default', }); // @ts-expect-error internal prop const trace = tracer.traceUtilities.getCurrentTrace(); (0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0); (0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(2); (0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(2); // See initial required span - should not complete yet // prettier-ignore const { spans: firstSpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('hello', 0)} Time: ${50} `; (0, processSpans_1.processSpans)(firstSpans, traceManager); (0, vitest_1.expect)(reportFn).not.toHaveBeenCalled(); // Now add an additional required span tracer.addRequirementsToCurrentTraceOnly({ additionalRequiredSpans: [{ name: 'added-required' }], }); // @ts-expect-error internal prop const traceRecreated = tracer.traceUtilities.getCurrentTrace(); (0, vitest_1.expect)(traceRecreated).not.toBe(trace); (0, vitest_1.expect)(traceRecreated?.definition.requiredSpans).toHaveLength(3); (0, vitest_1.expect)(traceRecreated?.definition.interruptOnSpans).toHaveLength(3); // two required spans left: (0, vitest_1.expect)(traceRecreated?.stateMachine.successfullyMatchedRequiredSpanMatchers .size).toBe(1); // See the added required span - now should complete // prettier-ignore const { spans: secondSpans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('initial-required', 50)}----${(0, makeTimeline_1.Render)('added-required', 0)} Time: ${100} ${150} `; (0, processSpans_1.processSpans)(secondSpans, traceManager); (0, vitest_1.expect)(traceRecreated?.stateMachine.successfullyMatchedRequiredSpanMatchers .size).toBe(3); // Verify trace completed (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.status).toBe('ok'); (0, vitest_1.expect)(report.duration).toBe(150); // Verify that previous spans were preserved const recordedSpanNames = report.entries.map((s) => s.span.name); (0, vitest_1.expect)(recordedSpanNames).toEqual([ 'hello', 'initial-required', 'added-required', ]); (0, vitest_1.expect)(traceManager.currentTracerContext).toBeUndefined(); }); }); (0, vitest_1.describe)('adding requiredSpans', () => { (0, vitest_1.it)('adds requiredSpans when starting a trace', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'ticket.basic-operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [{ name: 'orig-end' }], variants: { cold_boot: { timeout: 10_000 }, }, }); const traceId = tracer.start({ relatedTo: { id: '1' }, variant: 'cold_boot', }, { additionalRequiredSpans: [{ name: 'additional-end' }], }); (0, vitest_1.expect)(traceId).toBe('trace-id'); // @ts-expect-error internals const trace = tracer.traceUtilities.getCurrentTrace(); (0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(2); (0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0); (0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(2); // prettier-ignore const { spans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('middle', 0)}-----${(0, makeTimeline_1.Render)('orig-end', 0)}----${(0, makeTimeline_1.Render)('additional-end', 0)} Time: ${0} ${50} ${100} ${150} `; (0, processSpans_1.processSpans)(spans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); (0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(2); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.entries.map((spanAndAnnotation) => spanAndAnnotation.span.performanceEntry)).toMatchInlineSnapshot(` events | start middle orig-end additional-end timeline | |-<⋯ +50 ⋯>-|-<⋯ +50 ⋯>-|-<⋯ +50 ⋯>-| time (ms) | 0 50 100 150 `); (0, vitest_1.expect)(report.name).toBe('ticket.basic-operation'); (0, vitest_1.expect)(report.duration).toBe(150); (0, vitest_1.expect)(report.status).toBe('ok'); (0, vitest_1.expect)(report.interruptionReason).toBeUndefined(); }); (0, vitest_1.it)('adds requiredSpans when creating a trace, and then again after transitioning to active, and again after start', () => { const traceManager = new TraceManager_1.TraceManager({ relationSchemas: { test: { id: String } }, reportFn: getReportFn(), generateId, reportErrorFn, }); const tracer = traceManager.createTracer({ name: 'ticket.basic-operation', type: 'operation', relationSchemaName: 'test', requiredSpans: [ function origEnd({ span }) { return span.name === 'orig-end'; }, ], variants: { cold_boot: { timeout: 10_000, additionalRequiredSpans: [ function variantEnd({ span }) { return span.name === 'variant-end'; }, ], }, }, }); const traceId = tracer.createDraft({ variant: 'cold_boot', }, { additionalRequiredSpans: [ function draftEnd({ span }) { return span.name === 'draft-end'; }, ], }); tracer.transitionDraftToActive({ relatedTo: { id: '1' }, additionalRequiredSpans: [ function transitionToActiveEnd({ span }) { return span.name === 'transition-to-active-end'; }, ], }); tracer.addRequirementsToCurrentTraceOnly({ additionalRequiredSpans: [ function activeEnd({ span }) { return span.name === 'active-end'; }, ], }); // @ts-expect-error internals const trace = tracer.traceUtilities.getCurrentTrace(); (0, vitest_1.expect)(trace?.definition.requiredSpans).toHaveLength(5); (0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(0); (0, vitest_1.expect)(trace?.definition.interruptOnSpans).toHaveLength(5); // prettier-ignore const { spans } = (0, makeTimeline_1.getSpansFromTimeline) ` Events: ${(0, makeTimeline_1.Render)('start', 0)}-----${(0, makeTimeline_1.Render)('orig-end', 0)}----${(0, makeTimeline_1.Render)('variant-end', 0)}----${(0, makeTimeline_1.Render)('draft-end', 0)}----${(0, makeTimeline_1.Render)('transition-to-active-end', 0)}---${(0, makeTimeline_1.Render)('active-end', 0)} Time: ${0} ${50} ${100} ${150} ${200} ${250} `; (0, processSpans_1.processSpans)(spans, traceManager); (0, vitest_1.expect)(reportFn).toHaveBeenCalled(); (0, vitest_1.expect)(trace?.stateMachine.successfullyMatchedRequiredSpanMatchers.size).toBe(5); const report = reportFn.mock.calls[0][0]; (0, vitest_1.expect)(report.entries.map((spanAndAnnotation) => spanAndAnnotation.span.performanceEntry)).toMatchInlineSnapshot(` events | active-end events | start orig-end variant-end draft-end transition-to-active-end timeline | |-----------|------------|------------|------------|------------|- time (ms) | 0 50 100 150 200 250 `); (0, vitest_1.expect)(report.name).toBe('ticket.basic-operation'); (0, vitest_1.expect)(report.duration).toBe(250); (0, vitest_1.expect)(report.status).toBe('ok'); (0, vitest_1.expect)(report.interruptionReason).toBeUndefined(); }); }); }); //# sourceMappingURL=Tracer.test.js.map