From c9bd3402246cb4e91cc270321ca0ac080b88129f Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 4 Jun 2025 15:53:22 +0200 Subject: [PATCH 1/5] test(vue): Add tests for Vue tracing mixins --- .../vue/test/tracing/tracingMixin.test.ts | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 packages/vue/test/tracing/tracingMixin.test.ts diff --git a/packages/vue/test/tracing/tracingMixin.test.ts b/packages/vue/test/tracing/tracingMixin.test.ts new file mode 100644 index 000000000000..cb0bc16092cb --- /dev/null +++ b/packages/vue/test/tracing/tracingMixin.test.ts @@ -0,0 +1,238 @@ +import { getActiveSpan, startInactiveSpan } from '@sentry/browser'; +import type { Mock } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { DEFAULT_HOOKS } from '../../src/constants'; +import { createTracingMixins } from '../../src/tracing'; + +vi.mock('@sentry/browser', () => { + return { + getActiveSpan: vi.fn(), + startInactiveSpan: vi.fn().mockImplementation(({ name, op }) => { + return { + end: vi.fn(), + startChild: vi.fn(), + name, + op, + }; + }), + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN: 'sentry.origin', + }; +}); + +vi.mock('../../src/vendor/components', () => { + return { + formatComponentName: vi.fn().mockImplementation(vm => { + return vm.componentName || 'TestComponent'; + }), + }; +}); + +const mockSpanFactory = (): { name?: string; op?: string; end: Mock; startChild: Mock } => ({ + name: undefined, + op: undefined, + end: vi.fn(), + startChild: vi.fn(), +}); + +vi.useFakeTimers(); + +describe('Vue Tracing Mixins', () => { + let mockVueInstance: any; + let mockRootInstance: any; + + beforeEach(() => { + vi.clearAllMocks(); + + mockRootInstance = { + $root: null, + componentName: 'RootComponent', + $_sentrySpans: {}, + }; + mockRootInstance.$root = mockRootInstance; // Self-reference for root + + mockVueInstance = { + $root: mockRootInstance, + componentName: 'TestComponent', + $_sentrySpans: {}, + }; + + (getActiveSpan as any).mockReturnValue({ id: 'parent-span' }); + (startInactiveSpan as any).mockImplementation(({ name, op }: { name: string; op: string }) => { + const newSpan = mockSpanFactory(); + newSpan.name = name; + newSpan.op = op; + return newSpan; + }); + }); + + afterEach(() => { + vi.clearAllTimers(); + }); + + describe('Mixin Creation', () => { + it('should create mixins for default hooks', () => { + const mixins = createTracingMixins(); + + DEFAULT_HOOKS.forEach(hook => { + const hookPairs = { + mount: ['beforeMount', 'mounted'], + update: ['beforeUpdate', 'updated'], + destroy: ['beforeDestroy', 'destroyed'], + unmount: ['beforeUnmount', 'unmounted'], + create: ['beforeCreate', 'created'], + activate: ['activated', 'deactivated'], + }; + + if (hook in hookPairs) { + hookPairs[hook as keyof typeof hookPairs].forEach(lifecycleHook => { + expect(mixins).toHaveProperty(lifecycleHook); + // @ts-expect-error we check the type here + expect(typeof mixins[lifecycleHook]).toBe('function'); + }); + } + }); + }); + + it('should always include the activate and mount hooks', () => { + const mixins = createTracingMixins({ hooks: undefined }); + + expect(Object.keys(mixins)).toEqual(['activated', 'deactivated', 'beforeMount', 'mounted']); + }); + + it('should create mixins for custom hooks', () => { + const mixins = createTracingMixins({ hooks: ['update'] }); + + expect(Object.keys(mixins)).toEqual([ + 'beforeUpdate', + 'updated', + 'activated', + 'deactivated', + 'beforeMount', + 'mounted', + ]); + }); + }); + + describe('Root Component Behavior', () => { + it('should always create root span for root component regardless of tracking options', () => { + const mixins = createTracingMixins({ trackComponents: false }); + + mixins.beforeMount.call(mockRootInstance); + + expect(startInactiveSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Application Render', + op: 'ui.vue.render', + }), + ); + }); + + it('should finish root span on timer after component spans end', () => { + // todo/fixme: This root span is only finished if trackComponents is true --> it should probably be always finished + const mixins = createTracingMixins({ trackComponents: true, timeout: 1000 }); + const rootMockSpan = mockSpanFactory(); + mockRootInstance.$_sentryRootSpan = rootMockSpan; + + // Create and finish a component span + mixins.beforeMount.call(mockVueInstance); + mixins.mounted.call(mockVueInstance); + + // Root span should not end immediately + expect(rootMockSpan.end).not.toHaveBeenCalled(); + + // After timeout, root span should end + vi.advanceTimersByTime(1001); + expect(rootMockSpan.end).toHaveBeenCalled(); + }); + }); + + describe('Component Span Lifecycle', () => { + it('should create and end spans correctly through lifecycle hooks', () => { + const mixins = createTracingMixins({ trackComponents: true }); + + // 1. Create span in "before" hook + mixins.beforeMount.call(mockVueInstance); + + // Verify span was created with correct details + expect(startInactiveSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Vue TestComponent', + op: 'ui.vue.mount', + }), + ); + expect(mockVueInstance.$_sentrySpans.mount).toBeDefined(); + + // 2. Get the span for verification + const componentSpan = mockVueInstance.$_sentrySpans.mount; + + // 3. End span in "after" hook + mixins.mounted.call(mockVueInstance); + expect(componentSpan.end).toHaveBeenCalled(); + }); + + it('should clean up existing spans when creating new ones', () => { + const mixins = createTracingMixins({ trackComponents: true }); + + // Create an existing span first + const oldSpan = mockSpanFactory(); + mockVueInstance.$_sentrySpans.mount = oldSpan; + + // Create a new span for the same operation + mixins.beforeMount.call(mockVueInstance); + + // Verify old span was ended and new span was created + expect(oldSpan.end).toHaveBeenCalled(); + expect(mockVueInstance.$_sentrySpans.mount).not.toBe(oldSpan); + }); + + it('should gracefully handle when "after" hook is called without "before" hook', () => { + const mixins = createTracingMixins(); + + // Call mounted hook without calling beforeMount first + expect(() => mixins.mounted.call(mockVueInstance)).not.toThrow(); + }); + + it('should skip spans when no active root span (transaction) exists', () => { + const mixins = createTracingMixins({ trackComponents: true }); + + // Remove active spans + (getActiveSpan as any).mockReturnValue(null); + mockRootInstance.$_sentryRootSpan = null; + + // Try to create a span + mixins.beforeMount.call(mockVueInstance); + + // No span should be created + expect(startInactiveSpan).not.toHaveBeenCalled(); + }); + }); + + describe('Component Tracking Options', () => { + it('should respect tracking configuration options', () => { + // Test different tracking configurations with the same component + const runTracingTest = (trackComponents: boolean | string[] | undefined, shouldTrack: boolean) => { + vi.clearAllMocks(); + const mixins = createTracingMixins({ trackComponents }); + mixins.beforeMount.call(mockVueInstance); + + if (shouldTrack) { + expect(startInactiveSpan).toHaveBeenCalled(); + } else { + expect(startInactiveSpan).not.toHaveBeenCalled(); + } + }; + + // Test all tracking configurations + runTracingTest(undefined, false); // Default - don't track + runTracingTest(false, false); // Explicitly disabled + runTracingTest(true, true); // Track all components + runTracingTest(['TestComponent'], true); // Track by name (match) + + // Test component not in tracking list + vi.clearAllMocks(); + const mixins = createTracingMixins({ trackComponents: ['OtherComponent'] }); + mixins.beforeMount.call(mockVueInstance); // TestComponent + expect(startInactiveSpan).not.toHaveBeenCalled(); + }); + }); +}); From d5ba31ef8a6defd3e88b923cf1c15a06323022fb Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 5 Jun 2025 11:20:46 +0200 Subject: [PATCH 2/5] review suggestions --- .../vue/test/tracing/tracingMixin.test.ts | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/vue/test/tracing/tracingMixin.test.ts b/packages/vue/test/tracing/tracingMixin.test.ts index cb0bc16092cb..760e4a516d89 100644 --- a/packages/vue/test/tracing/tracingMixin.test.ts +++ b/packages/vue/test/tracing/tracingMixin.test.ts @@ -114,7 +114,7 @@ describe('Vue Tracing Mixins', () => { }); describe('Root Component Behavior', () => { - it('should always create root span for root component regardless of tracking options', () => { + it('should always create a span for the Vue root component regardless of tracking options', () => { const mixins = createTracingMixins({ trackComponents: false }); mixins.beforeMount.call(mockRootInstance); @@ -208,28 +208,30 @@ describe('Vue Tracing Mixins', () => { }); describe('Component Tracking Options', () => { - it('should respect tracking configuration options', () => { - // Test different tracking configurations with the same component - const runTracingTest = (trackComponents: boolean | string[] | undefined, shouldTrack: boolean) => { - vi.clearAllMocks(); - const mixins = createTracingMixins({ trackComponents }); - mixins.beforeMount.call(mockVueInstance); - - if (shouldTrack) { - expect(startInactiveSpan).toHaveBeenCalled(); - } else { - expect(startInactiveSpan).not.toHaveBeenCalled(); - } - }; + it.each([ + { trackComponents: undefined, expected: false, description: 'defaults to not tracking components' }, + { trackComponents: false, expected: false, description: 'does not track when explicitly disabled' }, + ])('$description', ({ trackComponents }) => { + const mixins = createTracingMixins({ trackComponents }); + mixins.beforeMount.call(mockVueInstance); + expect(startInactiveSpan).not.toHaveBeenCalled(); + }); - // Test all tracking configurations - runTracingTest(undefined, false); // Default - don't track - runTracingTest(false, false); // Explicitly disabled - runTracingTest(true, true); // Track all components - runTracingTest(['TestComponent'], true); // Track by name (match) + it.each([ + { trackComponents: true, description: 'tracks all components when enabled' }, + { trackComponents: ['TestComponent'], description: 'tracks components that match the name list' }, + ])('$description', ({ trackComponents }) => { + const mixins = createTracingMixins({ trackComponents }); + mixins.beforeMount.call(mockVueInstance); + expect(startInactiveSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'Vue TestComponent', + op: 'ui.vue.mount', + }), + ); + }); - // Test component not in tracking list - vi.clearAllMocks(); + it('does not track components not in the tracking list', () => { const mixins = createTracingMixins({ trackComponents: ['OtherComponent'] }); mixins.beforeMount.call(mockVueInstance); // TestComponent expect(startInactiveSpan).not.toHaveBeenCalled(); From 13364af7c3b9c8f62c3094e2d72900e45ff1930e Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 5 Jun 2025 11:27:24 +0200 Subject: [PATCH 3/5] change test naming --- packages/vue/test/tracing/tracingMixin.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vue/test/tracing/tracingMixin.test.ts b/packages/vue/test/tracing/tracingMixin.test.ts index 760e4a516d89..b9a92a4f7395 100644 --- a/packages/vue/test/tracing/tracingMixin.test.ts +++ b/packages/vue/test/tracing/tracingMixin.test.ts @@ -114,7 +114,7 @@ describe('Vue Tracing Mixins', () => { }); describe('Root Component Behavior', () => { - it('should always create a span for the Vue root component regardless of tracking options', () => { + it('should always create a root component span for the Vue root component regardless of tracking options', () => { const mixins = createTracingMixins({ trackComponents: false }); mixins.beforeMount.call(mockRootInstance); @@ -127,8 +127,8 @@ describe('Vue Tracing Mixins', () => { ); }); - it('should finish root span on timer after component spans end', () => { - // todo/fixme: This root span is only finished if trackComponents is true --> it should probably be always finished + it('should finish root component span on timer after component spans end', () => { + // todo/fixme: This root component span is only finished if trackComponents is true --> it should probably be always finished const mixins = createTracingMixins({ trackComponents: true, timeout: 1000 }); const rootMockSpan = mockSpanFactory(); mockRootInstance.$_sentryRootSpan = rootMockSpan; @@ -137,10 +137,10 @@ describe('Vue Tracing Mixins', () => { mixins.beforeMount.call(mockVueInstance); mixins.mounted.call(mockVueInstance); - // Root span should not end immediately + // Root component span should not end immediately expect(rootMockSpan.end).not.toHaveBeenCalled(); - // After timeout, root span should end + // After timeout, root component span should end vi.advanceTimersByTime(1001); expect(rootMockSpan.end).toHaveBeenCalled(); }); @@ -192,7 +192,7 @@ describe('Vue Tracing Mixins', () => { expect(() => mixins.mounted.call(mockVueInstance)).not.toThrow(); }); - it('should skip spans when no active root span (transaction) exists', () => { + it('should skip spans when no active root component span (transaction) exists', () => { const mixins = createTracingMixins({ trackComponents: true }); // Remove active spans From 7911f65d7f306062513224129b8f92cdde430c6d Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 5 Jun 2025 09:52:43 +0200 Subject: [PATCH 4/5] ref(vue): Clarify Vue tracing --- packages/vue/src/tracing.ts | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index e4e2fdb70f4e..408aec41c88b 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -79,6 +79,7 @@ export const createTracingMixins = (options: Partial = {}): Mixi mixins[internalHook] = function (this: VueSentry) { const isRoot = this.$root === this; + // 1. Root span creation if (isRoot) { this.$_sentryRootSpan = this.$_sentryRootSpan || @@ -92,35 +93,39 @@ export const createTracingMixins = (options: Partial = {}): Mixi }); } - // Skip components that we don't want to track to minimize the noise and give a more granular control to the user - const name = formatComponentName(this, false); + // 2. Component tracking filter + const componentName = formatComponentName(this, false); - const shouldTrack = Array.isArray(options.trackComponents) - ? findTrackComponent(options.trackComponents, name) - : options.trackComponents; + const shouldTrack = + isRoot || // We always want to track the root component + (Array.isArray(options.trackComponents) + ? findTrackComponent(options.trackComponents, componentName) + : options.trackComponents); - // We always want to track root component - if (!isRoot && !shouldTrack) { + if (!shouldTrack) { return; } this.$_sentrySpans = this.$_sentrySpans || {}; - // Start a new span if current hook is a 'before' hook. - // Otherwise, retrieve the current span and finish it. - if (internalHook == internalHooks[0]) { - const activeSpan = this.$root?.$_sentryRootSpan || getActiveSpan(); + // 3. Span lifecycle management based on the hook type + const isBeforeHook = internalHook === internalHooks[0]; + const activeSpan = this.$root?.$_sentryRootSpan || getActiveSpan(); + + if (isBeforeHook) { + // Starting a new span in the "before" hook if (activeSpan) { - // Cancel old span for this hook operation in case it didn't get cleaned up. We're not actually sure if it - // will ever be the case that cleanup hooks re not called, but we had users report that spans didn't get - // finished so we finish the span before starting a new one, just to be sure. + // Cancel any existing span for this operation (safety measure) + // We're actually not sure if it will ever be the case that cleanup hooks were not called. + // However, we had users report that spans didn't get finished, so we finished the span before + // starting a new one, just to be sure. const oldSpan = this.$_sentrySpans[operation]; if (oldSpan) { oldSpan.end(); } this.$_sentrySpans[operation] = startInactiveSpan({ - name: `Vue ${name}`, + name: `Vue ${componentName}`, op: `${VUE_OP}.${operation}`, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue', @@ -134,9 +139,10 @@ export const createTracingMixins = (options: Partial = {}): Mixi const span = this.$_sentrySpans[operation]; // The before hook did not start the tracking span, so the span was not added. // This is probably because it happened before there is an active transaction - if (!span) return; + if (!span) return; // Skip if no span was created in the "before" hook span.end(); + // For any "after" hook, also schedule the root span to finish finishRootSpan(this, timestampInSeconds(), options.timeout || 2000); } }; From 8ed01519c2ea6a134094ceb634934ee3267e2bd9 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Thu, 5 Jun 2025 11:38:51 +0200 Subject: [PATCH 5/5] rename to component spans --- packages/vue/src/tracing.ts | 48 +++++++++---------- .../vue/test/tracing/tracingMixin.test.ts | 16 +++---- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 408aec41c88b..5aadfdd876be 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -12,11 +12,11 @@ type Mixins = Parameters[0]; interface VueSentry extends ViewModel { readonly $root: VueSentry; - $_sentrySpans?: { + $_sentryComponentSpans?: { [key: string]: Span | undefined; }; - $_sentryRootSpan?: Span; - $_sentryRootSpanTimer?: ReturnType; + $_sentryRootComponentSpan?: Span; + $_sentryRootComponentSpanTimer?: ReturnType; } // Mappings from operation to corresponding lifecycle hook. @@ -31,16 +31,16 @@ const HOOKS: { [key in Operation]: Hook[] } = { update: ['beforeUpdate', 'updated'], }; -/** Finish top-level span and activity with a debounce configured using `timeout` option */ -function finishRootSpan(vm: VueSentry, timestamp: number, timeout: number): void { - if (vm.$_sentryRootSpanTimer) { - clearTimeout(vm.$_sentryRootSpanTimer); +/** Finish top-level component span and activity with a debounce configured using `timeout` option */ +function finishRootComponentSpan(vm: VueSentry, timestamp: number, timeout: number): void { + if (vm.$_sentryRootComponentSpanTimer) { + clearTimeout(vm.$_sentryRootComponentSpanTimer); } - vm.$_sentryRootSpanTimer = setTimeout(() => { - if (vm.$root?.$_sentryRootSpan) { - vm.$root.$_sentryRootSpan.end(timestamp); - vm.$root.$_sentryRootSpan = undefined; + vm.$_sentryRootComponentSpanTimer = setTimeout(() => { + if (vm.$root?.$_sentryRootComponentSpan) { + vm.$root.$_sentryRootComponentSpan.end(timestamp); + vm.$root.$_sentryRootComponentSpan = undefined; } }, timeout); } @@ -77,12 +77,12 @@ export const createTracingMixins = (options: Partial = {}): Mixi for (const internalHook of internalHooks) { mixins[internalHook] = function (this: VueSentry) { - const isRoot = this.$root === this; + const isRootComponent = this.$root === this; - // 1. Root span creation - if (isRoot) { - this.$_sentryRootSpan = - this.$_sentryRootSpan || + // 1. Root Component span creation + if (isRootComponent) { + this.$_sentryRootComponentSpan = + this.$_sentryRootComponentSpan || startInactiveSpan({ name: 'Application Render', op: `${VUE_OP}.render`, @@ -97,7 +97,7 @@ export const createTracingMixins = (options: Partial = {}): Mixi const componentName = formatComponentName(this, false); const shouldTrack = - isRoot || // We always want to track the root component + isRootComponent || // We always want to track the root component (Array.isArray(options.trackComponents) ? findTrackComponent(options.trackComponents, componentName) : options.trackComponents); @@ -106,11 +106,11 @@ export const createTracingMixins = (options: Partial = {}): Mixi return; } - this.$_sentrySpans = this.$_sentrySpans || {}; + this.$_sentryComponentSpans = this.$_sentryComponentSpans || {}; // 3. Span lifecycle management based on the hook type const isBeforeHook = internalHook === internalHooks[0]; - const activeSpan = this.$root?.$_sentryRootSpan || getActiveSpan(); + const activeSpan = this.$root?.$_sentryRootComponentSpan || getActiveSpan(); if (isBeforeHook) { // Starting a new span in the "before" hook @@ -119,12 +119,12 @@ export const createTracingMixins = (options: Partial = {}): Mixi // We're actually not sure if it will ever be the case that cleanup hooks were not called. // However, we had users report that spans didn't get finished, so we finished the span before // starting a new one, just to be sure. - const oldSpan = this.$_sentrySpans[operation]; + const oldSpan = this.$_sentryComponentSpans[operation]; if (oldSpan) { oldSpan.end(); } - this.$_sentrySpans[operation] = startInactiveSpan({ + this.$_sentryComponentSpans[operation] = startInactiveSpan({ name: `Vue ${componentName}`, op: `${VUE_OP}.${operation}`, attributes: { @@ -136,14 +136,14 @@ export const createTracingMixins = (options: Partial = {}): Mixi } } else { // The span should already be added via the first handler call (in the 'before' hook) - const span = this.$_sentrySpans[operation]; + const span = this.$_sentryComponentSpans[operation]; // The before hook did not start the tracking span, so the span was not added. // This is probably because it happened before there is an active transaction if (!span) return; // Skip if no span was created in the "before" hook span.end(); - // For any "after" hook, also schedule the root span to finish - finishRootSpan(this, timestampInSeconds(), options.timeout || 2000); + // For any "after" hook, also schedule the root component span to finish + finishRootComponentSpan(this, timestampInSeconds(), options.timeout || 2000); } }; } diff --git a/packages/vue/test/tracing/tracingMixin.test.ts b/packages/vue/test/tracing/tracingMixin.test.ts index b9a92a4f7395..d67690271ed2 100644 --- a/packages/vue/test/tracing/tracingMixin.test.ts +++ b/packages/vue/test/tracing/tracingMixin.test.ts @@ -46,14 +46,14 @@ describe('Vue Tracing Mixins', () => { mockRootInstance = { $root: null, componentName: 'RootComponent', - $_sentrySpans: {}, + $_sentryComponentSpans: {}, }; mockRootInstance.$root = mockRootInstance; // Self-reference for root mockVueInstance = { $root: mockRootInstance, componentName: 'TestComponent', - $_sentrySpans: {}, + $_sentryComponentSpans: {}, }; (getActiveSpan as any).mockReturnValue({ id: 'parent-span' }); @@ -131,7 +131,7 @@ describe('Vue Tracing Mixins', () => { // todo/fixme: This root component span is only finished if trackComponents is true --> it should probably be always finished const mixins = createTracingMixins({ trackComponents: true, timeout: 1000 }); const rootMockSpan = mockSpanFactory(); - mockRootInstance.$_sentryRootSpan = rootMockSpan; + mockRootInstance.$_sentryRootComponentSpan = rootMockSpan; // Create and finish a component span mixins.beforeMount.call(mockVueInstance); @@ -160,10 +160,10 @@ describe('Vue Tracing Mixins', () => { op: 'ui.vue.mount', }), ); - expect(mockVueInstance.$_sentrySpans.mount).toBeDefined(); + expect(mockVueInstance.$_sentryComponentSpans.mount).toBeDefined(); // 2. Get the span for verification - const componentSpan = mockVueInstance.$_sentrySpans.mount; + const componentSpan = mockVueInstance.$_sentryComponentSpans.mount; // 3. End span in "after" hook mixins.mounted.call(mockVueInstance); @@ -175,14 +175,14 @@ describe('Vue Tracing Mixins', () => { // Create an existing span first const oldSpan = mockSpanFactory(); - mockVueInstance.$_sentrySpans.mount = oldSpan; + mockVueInstance.$_sentryComponentSpans.mount = oldSpan; // Create a new span for the same operation mixins.beforeMount.call(mockVueInstance); // Verify old span was ended and new span was created expect(oldSpan.end).toHaveBeenCalled(); - expect(mockVueInstance.$_sentrySpans.mount).not.toBe(oldSpan); + expect(mockVueInstance.$_sentryComponentSpans.mount).not.toBe(oldSpan); }); it('should gracefully handle when "after" hook is called without "before" hook', () => { @@ -197,7 +197,7 @@ describe('Vue Tracing Mixins', () => { // Remove active spans (getActiveSpan as any).mockReturnValue(null); - mockRootInstance.$_sentryRootSpan = null; + mockRootInstance.$_sentryRootComponentSpan = null; // Try to create a span mixins.beforeMount.call(mockVueInstance);