From c8fcfe86abeb15c957ec2dac8627dfb593e63d52 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 5 Oct 2023 15:24:12 +0200 Subject: [PATCH] feat(vue): Expose `initVueApp` method to initialize vue app later --- packages/vue/src/index.ts | 2 +- packages/vue/src/sdk.ts | 22 ++++- packages/vue/src/types.ts | 8 +- packages/vue/test/integration/init.test.ts | 27 +++++-- .../vue/test/integration/initVueApp.test.ts | 81 +++++++++++++++++++ 5 files changed, 126 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..ba78a52227e0 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -1,6 +1,6 @@ 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 +43,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 +61,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): void { + const 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..18f70295af1b --- /dev/null +++ b/packages/vue/test/integration/initVueApp.test.ts @@ -0,0 +1,81 @@ +import { logger } from '@sentry/utils'; +import { createApp } from 'vue'; + +import * as Sentry from '../../src'; +import { 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(); + }); +});