From b0374653d6d9afd31f76958079c8b34fb9c4fa66 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 5 Oct 2023 15:24:12 +0200 Subject: [PATCH 1/2] feat(vue): Expose `initVueApp` method to initialize vue app later --- packages/vue/src/index.ts | 2 +- packages/vue/src/sdk.ts | 23 +++- packages/vue/src/types.ts | 8 +- packages/vue/test/integration/init.test.ts | 27 +++-- .../vue/test/integration/initVueApp.test.ts | 104 ++++++++++++++++++ 5 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 packages/vue/test/integration/initVueApp.test.ts diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 24a352aba99a..ebb9f3dc3be9 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,6 +1,6 @@ export * from '@sentry/browser'; -export { init } from './sdk'; +export { init, initVueApp } from './sdk'; export { vueRouterInstrumentation } from './router'; export { attachErrorHandler } from './errorhandler'; export { createTracingMixins } from './tracing'; diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index ecc879bccbd7..73ec46ec2d9e 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -1,6 +1,7 @@ +import type { BrowserClient } from '@sentry/browser'; import { init as browserInit, SDK_VERSION } from '@sentry/browser'; -import { hasTracingEnabled } from '@sentry/core'; -import { arrayify, GLOBAL_OBJ } from '@sentry/utils'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; +import { arrayify, GLOBAL_OBJ, logger } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; import { attachErrorHandler } from './errorhandler'; @@ -43,7 +44,7 @@ export function init( browserInit(options); - if (!options.Vue && !options.app) { + if (!options.Vue && !options.app && options.app !== false) { // eslint-disable-next-line no-console console.warn( `[@sentry/vue]: Misconfigured SDK. Vue specific errors will not be captured. @@ -61,6 +62,22 @@ Update your \`Sentry.init\` call with an appropriate config option: } } +/** + * Initialize Vue-specific error monitoring for a given Vue app. + */ +export function initVueApp(app: Vue, client?: BrowserClient): void { + const _client = client || getCurrentHub().getClient(); + const options = _client && (_client.getOptions() as Options); + + if (options) { + vueInit(app, options); + } else if (__DEBUG_BUILD__) { + logger.warn( + '[@sentry/vue]: Cannot initialize as no Client available. Make sure to call `Sentry.init` before calling `initVueApp()`.', + ); + } +} + const vueInit = (app: Vue, options: Options): void => { // Check app is not mounted yet - should be mounted _after_ init()! // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index 1cc39b97b887..6a728e96d48c 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -29,8 +29,12 @@ export interface Options extends TracingOptions, BrowserOptions { /** Vue constructor to be used inside the integration (as imported by `import Vue from 'vue'` in Vue2) */ Vue?: Vue; - /** Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3 ) */ - app?: Vue | Vue[]; + /** + * Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3 ) + * Set this to `false` to indicate you are purposefully _not_ setting up a Vue app right now, + * e.g. if you want to manually call `initVueApp` later. + */ + app?: Vue | Vue[] | false; /** * When set to `false`, Sentry will suppress reporting of all props data diff --git a/packages/vue/test/integration/init.test.ts b/packages/vue/test/integration/init.test.ts index a9936c97bc89..7735f3d8c56d 100644 --- a/packages/vue/test/integration/init.test.ts +++ b/packages/vue/test/integration/init.test.ts @@ -3,22 +3,17 @@ import { createApp } from 'vue'; import * as Sentry from './../../src'; describe('Sentry.init', () => { - let _consoleWarn: any; - let warnings: string[] = []; + let warnings: unknown[] = []; beforeEach(() => { warnings = []; - // eslint-disable-next-line no-console - _consoleWarn = console.warn; - // eslint-disable-next-line no-console - console.warn = jest.fn((message: string) => { + jest.spyOn(console, 'warn').mockImplementation((message: unknown) => { warnings.push(message); }); }); afterEach(() => { - // eslint-disable-next-line no-console - console.warn = _consoleWarn; + jest.clearAllMocks(); }); it('does not warn when correctly setup (Vue 3)', () => { @@ -90,4 +85,20 @@ Update your \`Sentry.init\` call with an appropriate config option: \`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`, ]); }); + + it('does not warn when passing app=false', () => { + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + Sentry.init({ + app: false, + defaultIntegrations: false, + }); + + app.mount(el); + + expect(warnings).toEqual([]); + }); }); diff --git a/packages/vue/test/integration/initVueApp.test.ts b/packages/vue/test/integration/initVueApp.test.ts new file mode 100644 index 000000000000..b6620f226b37 --- /dev/null +++ b/packages/vue/test/integration/initVueApp.test.ts @@ -0,0 +1,104 @@ +import { logger } from '@sentry/utils'; +import { createApp } from 'vue'; + +import * as Sentry from '../../src'; +import { createTransport, Hub, makeMain } from '../../src'; + +const PUBLIC_DSN = 'https://username@domain/123'; + +describe('Sentry.initVueApp', () => { + let loggerWarnings: unknown[] = []; + let warnings: unknown[] = []; + + beforeEach(() => { + warnings = []; + loggerWarnings = []; + + jest.spyOn(logger, 'warn').mockImplementation((message: unknown) => { + loggerWarnings.push(message); + }); + + jest.spyOn(console, 'warn').mockImplementation((message: unknown) => { + warnings.push(message); + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('warns when called before SDK.init()', () => { + const hub = new Hub(); + makeMain(hub); + + const app = createApp({ + template: '
hello
', + }); + + Sentry.initVueApp(app); + + expect(loggerWarnings).toEqual([ + '[@sentry/vue]: Cannot initialize as no Client available. Make sure to call `Sentry.init` before calling `initVueApp()`.', + ]); + expect(warnings).toEqual([]); + }); + + it('warns when mounting before SDK.initVueApp()', () => { + Sentry.init({ dsn: PUBLIC_DSN, app: false, autoSessionTracking: false }); + + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + app.mount(el); + + Sentry.initVueApp(app); + + expect(warnings).toEqual([ + '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', + ]); + expect(loggerWarnings).toEqual([]); + }); + + it('works when calling SDK.initVueApp()', () => { + Sentry.init({ dsn: PUBLIC_DSN, app: false, autoSessionTracking: false }); + + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + Sentry.initVueApp(app); + + app.mount(el); + + expect(warnings).toEqual([]); + expect(loggerWarnings).toEqual([]); + + expect(app.config.errorHandler).toBeDefined(); + }); + + it('allows to pass a client to SDK.initVueApp()', () => { + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + const client = new Sentry.BrowserClient({ + dsn: PUBLIC_DSN, + integrations: [], + stackParser: () => [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), + }); + + Sentry.initVueApp(app, client); + + app.mount(el); + + expect(warnings).toEqual([]); + expect(loggerWarnings).toEqual([]); + + expect(app.config.errorHandler).toBeDefined(); + }); +}); From 827475d1c77331dff8b081ab492e1b0c05cb3c7b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 5 Oct 2023 17:14:35 +0200 Subject: [PATCH 2/2] ref: Refactor to integration --- packages/vue/src/errorhandler.ts | 4 +- packages/vue/src/index.ts | 3 +- packages/vue/src/integration.ts | 99 ++++++++++++++++ packages/vue/src/sdk.ts | 111 +++--------------- packages/vue/src/types.ts | 10 +- .../test/integration/VueIntegration.test.ts | 68 +++++++++++ packages/vue/test/integration/init.test.ts | 42 +++++-- .../vue/test/integration/initVueApp.test.ts | 104 ---------------- 8 files changed, 223 insertions(+), 218 deletions(-) create mode 100644 packages/vue/src/integration.ts create mode 100644 packages/vue/test/integration/VueIntegration.test.ts delete mode 100644 packages/vue/test/integration/initVueApp.test.ts diff --git a/packages/vue/src/errorhandler.ts b/packages/vue/src/errorhandler.ts index 542d341c322f..900bed5a5074 100644 --- a/packages/vue/src/errorhandler.ts +++ b/packages/vue/src/errorhandler.ts @@ -1,12 +1,12 @@ import { getCurrentHub } from '@sentry/browser'; import { addExceptionMechanism } from '@sentry/utils'; -import type { Options, ViewModel, Vue } from './types'; +import type { ViewModel, Vue, VueOptions } from './types'; import { formatComponentName, generateComponentTrace } from './vendor/components'; type UnknownFunc = (...args: unknown[]) => void; -export const attachErrorHandler = (app: Vue, options: Options): void => { +export const attachErrorHandler = (app: Vue, options: VueOptions): void => { const { errorHandler, warnHandler, silent } = app.config; app.config.errorHandler = (error: Error, vm: ViewModel, lifecycleHook: string): void => { diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index ebb9f3dc3be9..6afcc0f60ae8 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,6 +1,7 @@ export * from '@sentry/browser'; -export { init, initVueApp } from './sdk'; +export { init } from './sdk'; export { vueRouterInstrumentation } from './router'; export { attachErrorHandler } from './errorhandler'; export { createTracingMixins } from './tracing'; +export { VueIntegration } from './integration'; diff --git a/packages/vue/src/integration.ts b/packages/vue/src/integration.ts new file mode 100644 index 000000000000..9a3969f5c415 --- /dev/null +++ b/packages/vue/src/integration.ts @@ -0,0 +1,99 @@ +import { hasTracingEnabled } from '@sentry/core'; +import type { Hub, Integration } from '@sentry/types'; +import { arrayify, GLOBAL_OBJ } from '@sentry/utils'; + +import { DEFAULT_HOOKS } from './constants'; +import { attachErrorHandler } from './errorhandler'; +import { createTracingMixins } from './tracing'; +import type { Options, Vue, VueOptions } from './types'; + +const globalWithVue = GLOBAL_OBJ as typeof GLOBAL_OBJ & { Vue: Vue }; + +const DEFAULT_CONFIG: VueOptions = { + Vue: globalWithVue.Vue, + attachProps: true, + logErrors: true, + hooks: DEFAULT_HOOKS, + timeout: 2000, + trackComponents: false, +}; + +/** + * Initialize Vue error & performance tracking. + */ +export class VueIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Vue'; + + /** + * @inheritDoc + */ + public name: string; + + private readonly _options: Partial; + + public constructor(options: Partial = {}) { + this.name = VueIntegration.id; + this._options = options; + } + + /** @inheritDoc */ + public setupOnce(_addGlobaleventProcessor: unknown, getCurrentHub: () => Hub): void { + this._setupIntegration(getCurrentHub()); + } + + /** Just here for easier testing */ + protected _setupIntegration(hub: Hub): void { + const client = hub.getClient(); + const options: Options = { ...DEFAULT_CONFIG, ...(client && client.getOptions()), ...this._options }; + + if (!options.Vue && !options.app) { + // eslint-disable-next-line no-console + console.warn( + `[@sentry/vue]: Misconfigured SDK. Vue specific errors will not be captured. +Update your \`Sentry.init\` call with an appropriate config option: +\`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`, + ); + return; + } + + if (options.app) { + const apps = arrayify(options.app); + apps.forEach(app => vueInit(app, options)); + } else if (options.Vue) { + vueInit(options.Vue, options); + } + } +} + +const vueInit = (app: Vue, options: Options): void => { + // Check app is not mounted yet - should be mounted _after_ init()! + // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it + // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 + const appWithInstance = app as Vue & { + _instance?: { + isMounted?: boolean; + }; + }; + + const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; + if (isMounted === true) { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', + ); + } + + attachErrorHandler(app, options); + + if (hasTracingEnabled(options)) { + app.mixin( + createTracingMixins({ + ...options, + ...options.tracingOptions, + }), + ); + } +}; diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index 73ec46ec2d9e..21d7246f503c 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -1,35 +1,7 @@ -import type { BrowserClient } from '@sentry/browser'; -import { init as browserInit, SDK_VERSION } from '@sentry/browser'; -import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; -import { arrayify, GLOBAL_OBJ, logger } from '@sentry/utils'; +import { defaultIntegrations, init as browserInit, SDK_VERSION } from '@sentry/browser'; -import { DEFAULT_HOOKS } from './constants'; -import { attachErrorHandler } from './errorhandler'; -import { createTracingMixins } from './tracing'; -import type { Options, TracingOptions, Vue } from './types'; - -const globalWithVue = GLOBAL_OBJ as typeof GLOBAL_OBJ & { Vue: Vue }; - -const DEFAULT_CONFIG: Options = { - Vue: globalWithVue.Vue, - attachProps: true, - logErrors: true, - hooks: DEFAULT_HOOKS, - timeout: 2000, - trackComponents: false, - _metadata: { - sdk: { - name: 'sentry.javascript.vue', - packages: [ - { - name: 'npm:@sentry/vue', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }, - }, -}; +import { VueIntegration } from './integration'; +import type { Options, TracingOptions } from './types'; /** * Inits the Vue SDK @@ -38,72 +10,21 @@ export function init( config: Partial & { tracingOptions: Partial }> = {}, ): void { const options = { - ...DEFAULT_CONFIG, + _metadata: { + sdk: { + name: 'sentry.javascript.vue', + packages: [ + { + name: 'npm:@sentry/vue', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }, + }, + defaultIntegrations: [...defaultIntegrations, new VueIntegration()], ...config, }; browserInit(options); - - if (!options.Vue && !options.app && options.app !== false) { - // eslint-disable-next-line no-console - console.warn( - `[@sentry/vue]: Misconfigured SDK. Vue specific errors will not be captured. -Update your \`Sentry.init\` call with an appropriate config option: -\`app\` (Application Instance - Vue 3) or \`Vue\` (Vue Constructor - Vue 2).`, - ); - return; - } - - if (options.app) { - const apps = arrayify(options.app); - apps.forEach(app => vueInit(app, options)); - } else if (options.Vue) { - vueInit(options.Vue, options); - } -} - -/** - * Initialize Vue-specific error monitoring for a given Vue app. - */ -export function initVueApp(app: Vue, client?: BrowserClient): void { - const _client = client || getCurrentHub().getClient(); - const options = _client && (_client.getOptions() as Options); - - if (options) { - vueInit(app, options); - } else if (__DEBUG_BUILD__) { - logger.warn( - '[@sentry/vue]: Cannot initialize as no Client available. Make sure to call `Sentry.init` before calling `initVueApp()`.', - ); - } } - -const vueInit = (app: Vue, options: Options): void => { - // Check app is not mounted yet - should be mounted _after_ init()! - // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it - // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 - const appWithInstance = app as Vue & { - _instance?: { - isMounted?: boolean; - }; - }; - - const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; - if (isMounted === true) { - // eslint-disable-next-line no-console - console.warn( - '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', - ); - } - - attachErrorHandler(app, options); - - if (hasTracingEnabled(options)) { - app.mixin( - createTracingMixins({ - ...options, - ...options.tracingOptions, - }), - ); - } -}; diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index 6a728e96d48c..2a1ee6d89046 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -25,16 +25,14 @@ export type ViewModel = { }; }; -export interface Options extends TracingOptions, BrowserOptions { +export interface VueOptions extends TracingOptions { /** Vue constructor to be used inside the integration (as imported by `import Vue from 'vue'` in Vue2) */ Vue?: Vue; /** - * Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3 ) - * Set this to `false` to indicate you are purposefully _not_ setting up a Vue app right now, - * e.g. if you want to manually call `initVueApp` later. + * Vue app instance(s) to be used inside the integration (as generated by `createApp` in Vue3). */ - app?: Vue | Vue[] | false; + app?: Vue | Vue[]; /** * When set to `false`, Sentry will suppress reporting of all props data @@ -52,6 +50,8 @@ export interface Options extends TracingOptions, BrowserOptions { tracingOptions?: Partial; } +export interface Options extends BrowserOptions, VueOptions {} + /** Vue specific configuration for Tracing Integration */ export interface TracingOptions { /** diff --git a/packages/vue/test/integration/VueIntegration.test.ts b/packages/vue/test/integration/VueIntegration.test.ts new file mode 100644 index 000000000000..22f53df4c498 --- /dev/null +++ b/packages/vue/test/integration/VueIntegration.test.ts @@ -0,0 +1,68 @@ +import { logger } from '@sentry/utils'; +import { createApp } from 'vue'; + +import * as Sentry from '../../src'; + +const PUBLIC_DSN = 'https://username@domain/123'; + +describe('Sentry.VueIntegration', () => { + let loggerWarnings: unknown[] = []; + let warnings: unknown[] = []; + + beforeEach(() => { + warnings = []; + loggerWarnings = []; + + jest.spyOn(logger, 'warn').mockImplementation((message: unknown) => { + loggerWarnings.push(message); + }); + + jest.spyOn(console, 'warn').mockImplementation((message: unknown) => { + warnings.push(message); + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('allows to initialize integration later', () => { + Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false }); + + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + // This would normally happen through client.addIntegration() + const integration = new Sentry.VueIntegration({ app }); + integration['_setupIntegration'](Sentry.getCurrentHub()); + + app.mount(el); + + expect(warnings).toEqual([]); + expect(loggerWarnings).toEqual([]); + + expect(app.config.errorHandler).toBeDefined(); + }); + + it('warns when mounting before SDK.VueIntegration', () => { + Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false }); + + const el = document.createElement('div'); + const app = createApp({ + template: '
hello
', + }); + + app.mount(el); + + // This would normally happen through client.addIntegration() + const integration = new Sentry.VueIntegration({ app }); + integration['_setupIntegration'](Sentry.getCurrentHub()); + + expect(warnings).toEqual([ + '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', + ]); + expect(loggerWarnings).toEqual([]); + }); +}); diff --git a/packages/vue/test/integration/init.test.ts b/packages/vue/test/integration/init.test.ts index 7735f3d8c56d..e176e5b1691c 100644 --- a/packages/vue/test/integration/init.test.ts +++ b/packages/vue/test/integration/init.test.ts @@ -1,7 +1,11 @@ import { createApp } from 'vue'; +import { VueIntegration } from '../../src/integration'; +import type { Options } from '../../src/types'; import * as Sentry from './../../src'; +const PUBLIC_DSN = 'https://username@domain/123'; + describe('Sentry.init', () => { let warnings: unknown[] = []; @@ -22,9 +26,8 @@ describe('Sentry.init', () => { template: '
hello
', }); - Sentry.init({ + runInit({ app, - defaultIntegrations: false, }); app.mount(el); @@ -38,10 +41,9 @@ describe('Sentry.init', () => { template: '
hello
', }); - Sentry.init({ + runInit({ // this is a bit "hacky", but good enough to test what we want Vue: app, - defaultIntegrations: false, }); app.mount(el); @@ -57,9 +59,8 @@ describe('Sentry.init', () => { app.mount(el); - Sentry.init({ + runInit({ app, - defaultIntegrations: false, }); expect(warnings).toEqual([ @@ -73,9 +74,7 @@ describe('Sentry.init', () => { template: '
hello
', }); - Sentry.init({ - defaultIntegrations: false, - }); + runInit({}); app.mount(el); @@ -86,15 +85,16 @@ Update your \`Sentry.init\` call with an appropriate config option: ]); }); - it('does not warn when passing app=false', () => { + it('does not warn when skipping Vue integration', () => { const el = document.createElement('div'); const app = createApp({ template: '
hello
', }); Sentry.init({ - app: false, + dsn: PUBLIC_DSN, defaultIntegrations: false, + integrations: [], }); app.mount(el); @@ -102,3 +102,23 @@ Update your \`Sentry.init\` call with an appropriate config option: expect(warnings).toEqual([]); }); }); + +function runInit(options: Partial): void { + const hasRunBefore = Sentry.getCurrentHub().getIntegration(VueIntegration); + + const integration = new VueIntegration(); + + Sentry.init({ + dsn: PUBLIC_DSN, + defaultIntegrations: false, + integrations: [integration], + ...options, + }); + + // Because our integrations API is terrible to test, we need to make sure to check + // If we've already had this integration registered before + // if that's the case, `setup()` will not be run, so we need to manually run it :( + if (hasRunBefore) { + integration['_setupIntegration'](Sentry.getCurrentHub()); + } +} diff --git a/packages/vue/test/integration/initVueApp.test.ts b/packages/vue/test/integration/initVueApp.test.ts deleted file mode 100644 index b6620f226b37..000000000000 --- a/packages/vue/test/integration/initVueApp.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { logger } from '@sentry/utils'; -import { createApp } from 'vue'; - -import * as Sentry from '../../src'; -import { createTransport, Hub, makeMain } from '../../src'; - -const PUBLIC_DSN = 'https://username@domain/123'; - -describe('Sentry.initVueApp', () => { - let loggerWarnings: unknown[] = []; - let warnings: unknown[] = []; - - beforeEach(() => { - warnings = []; - loggerWarnings = []; - - jest.spyOn(logger, 'warn').mockImplementation((message: unknown) => { - loggerWarnings.push(message); - }); - - jest.spyOn(console, 'warn').mockImplementation((message: unknown) => { - warnings.push(message); - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('warns when called before SDK.init()', () => { - const hub = new Hub(); - makeMain(hub); - - const app = createApp({ - template: '
hello
', - }); - - Sentry.initVueApp(app); - - expect(loggerWarnings).toEqual([ - '[@sentry/vue]: Cannot initialize as no Client available. Make sure to call `Sentry.init` before calling `initVueApp()`.', - ]); - expect(warnings).toEqual([]); - }); - - it('warns when mounting before SDK.initVueApp()', () => { - Sentry.init({ dsn: PUBLIC_DSN, app: false, autoSessionTracking: false }); - - const el = document.createElement('div'); - const app = createApp({ - template: '
hello
', - }); - - app.mount(el); - - Sentry.initVueApp(app); - - expect(warnings).toEqual([ - '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', - ]); - expect(loggerWarnings).toEqual([]); - }); - - it('works when calling SDK.initVueApp()', () => { - Sentry.init({ dsn: PUBLIC_DSN, app: false, autoSessionTracking: false }); - - const el = document.createElement('div'); - const app = createApp({ - template: '
hello
', - }); - - Sentry.initVueApp(app); - - app.mount(el); - - expect(warnings).toEqual([]); - expect(loggerWarnings).toEqual([]); - - expect(app.config.errorHandler).toBeDefined(); - }); - - it('allows to pass a client to SDK.initVueApp()', () => { - const el = document.createElement('div'); - const app = createApp({ - template: '
hello
', - }); - - const client = new Sentry.BrowserClient({ - dsn: PUBLIC_DSN, - integrations: [], - stackParser: () => [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), - }); - - Sentry.initVueApp(app, client); - - app.mount(el); - - expect(warnings).toEqual([]); - expect(loggerWarnings).toEqual([]); - - expect(app.config.errorHandler).toBeDefined(); - }); -});