diff --git a/packages/solidjs/package.json b/packages/solidjs/package.json index 7b4317c79d60..ca29e06d6c46 100644 --- a/packages/solidjs/package.json +++ b/packages/solidjs/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@solidjs/testing-library": "0.8.5", + "@solidjs/router": "^0.13.5", "solid-js": "^1.8.11", "vite-plugin-solid": "^2.8.2" }, diff --git a/packages/solidjs/test/solidrouter.test.tsx b/packages/solidjs/test/solidrouter.test.tsx new file mode 100644 index 000000000000..4530eaa75871 --- /dev/null +++ b/packages/solidjs/test/solidrouter.test.tsx @@ -0,0 +1,201 @@ +import { spanToJSON } from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + createTransport, + getCurrentScope, + setCurrentClient, +} from '@sentry/core'; +import { MemoryRouter, Navigate, Route, createMemoryHistory, useBeforeLeave, useLocation } from '@solidjs/router'; +import { render } from '@solidjs/testing-library'; +import { vi } from 'vitest'; + +import { BrowserClient } from '../src'; +import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '../src/solidrouter'; + +// solid router uses `window.scrollTo` when navigating +vi.spyOn(global, 'scrollTo').mockImplementation(() => {}); + +const renderRouter = (SentryRouter, history) => + render(() => ( + +
Home
} /> + +
About
} /> +
us
} /> +
+ +
User
} /> +
Post
} /> +
+ } /> + } /> + } /> + } /> +
+ )); + +describe('solidRouterBrowserTracingIntegration', () => { + function createMockBrowserClient(): BrowserClient { + return new BrowserClient({ + integrations: [], + tracesSampleRate: 1, + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), + stackParser: () => [], + }); + } + + beforeEach(() => { + vi.clearAllMocks(); + getCurrentScope().setClient(undefined); + }); + + it('starts a pageload span', () => { + const spanStartMock = vi.fn(); + + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.on('spanStart', span => spanStartMock(spanToJSON(span))); + client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })); + + const history = createMemoryHistory(); + history.set({ value: '/' }); + + expect(spanStartMock).toHaveBeenCalledWith( + expect.objectContaining({ + op: 'pageload', + description: '/', + data: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser', + }), + }), + ); + }); + + it('skips pageload span, with `instrumentPageLoad: false`', () => { + const spanStartMock = vi.fn(); + + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.on('spanStart', span => spanStartMock(spanToJSON(span))); + client.addIntegration( + solidRouterBrowserTracingIntegration({ + instrumentPageLoad: false, + useBeforeLeave, + useLocation, + }), + ); + const SentryRouter = withSentryRouterRouting(MemoryRouter); + + const history = createMemoryHistory(); + history.set({ value: '/' }); + + renderRouter(SentryRouter, history); + + expect(spanStartMock).not.toHaveBeenCalledWith( + expect.objectContaining({ + op: 'pageload', + description: '/', + data: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser', + }), + }), + ); + }); + + it.each([ + ['', '/navigate-to-about', '/about'], + ['for nested navigation', '/navigate-to-about-us', '/about/us'], + ['for navigation with param', '/navigate-to-user', '/user/5'], + ['for nested navigation with params', '/navigate-to-user-post', '/user/5/post/12'], + ])('starts a navigation span %s', (_itDescription, navigationPath, path) => { + const spanStartMock = vi.fn(); + + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.on('spanStart', span => { + spanStartMock(spanToJSON(span)); + }); + client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })); + const SentryRouter = withSentryRouterRouting(MemoryRouter); + + const history = createMemoryHistory(); + history.set({ value: navigationPath }); + + renderRouter(SentryRouter, history); + + expect(spanStartMock).toHaveBeenCalledWith( + expect.objectContaining({ + op: 'navigation', + description: path, + data: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solidjs.solidrouter', + }), + }), + ); + }); + + it('skips navigation span, with `instrumentNavigation: false`', () => { + const spanStartMock = vi.fn(); + + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.on('spanStart', span => spanStartMock(spanToJSON(span))); + client.addIntegration( + solidRouterBrowserTracingIntegration({ + instrumentNavigation: false, + useBeforeLeave, + useLocation, + }), + ); + const SentryRouter = withSentryRouterRouting(MemoryRouter); + + const history = createMemoryHistory(); + history.set({ value: '/navigate-to-about' }); + + renderRouter(SentryRouter, history); + + expect(spanStartMock).not.toHaveBeenCalledWith( + expect.objectContaining({ + op: 'navigation', + description: '/about', + data: expect.objectContaining({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solidjs.solidrouter', + }), + }), + ); + }); + + it("updates the scope's `transactionName` on a navigation", () => { + const spanStartMock = vi.fn(); + + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.on('spanStart', span => { + spanStartMock(spanToJSON(span)); + }); + client.addIntegration(solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })); + const SentryRouter = withSentryRouterRouting(MemoryRouter); + + const history = createMemoryHistory(); + history.set({ value: '/navigate-to-about' }); + + renderRouter(SentryRouter, history); + + expect(getCurrentScope().getScopeData()?.transactionName).toBe('/about'); + }); +}); diff --git a/yarn.lock b/yarn.lock index e2fbf4e54e57..31871350a302 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7768,6 +7768,11 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@solidjs/router@^0.13.5": + version "0.13.5" + resolved "https://registry.yarnpkg.com/@solidjs/router/-/router-0.13.5.tgz#62ee37f63d2b5f74937903d64d04ec9a2b4223cf" + integrity sha512-I/bR5ZHCz2Dx80qL+6uGwSdclqXRqoT49SJ5cvLbOuT3HnYysSIxSfULCTWUMLFVcgPh5GrdHV6KwEoyrbPZZA== + "@solidjs/testing-library@0.8.5": version "0.8.5" resolved "https://registry.yarnpkg.com/@solidjs/testing-library/-/testing-library-0.8.5.tgz#97061b2286d8641bd43bf474e624c3bb47e486a6"