setupDocs.spec.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import {ProjectKeysFixture} from 'sentry-fixture/projectKeys';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
  4. import {OnboardingContextProvider} from 'sentry/components/onboarding/onboardingContext';
  5. import {ProductSolution} from 'sentry/components/onboarding/productSelection';
  6. import ProjectsStore from 'sentry/stores/projectsStore';
  7. import type {OnboardingRecentCreatedProject, Organization, Project} from 'sentry/types';
  8. import SetupDocs from 'sentry/views/onboarding/setupDocs';
  9. const PROJECT_KEY = ProjectKeysFixture()[0];
  10. function renderMockRequests({
  11. project,
  12. orgSlug,
  13. }: {
  14. orgSlug: Organization['slug'];
  15. project: Project;
  16. }) {
  17. MockApiClient.addMockResponse({
  18. url: `/projects/${orgSlug}/${project.slug}/`,
  19. body: project,
  20. });
  21. MockApiClient.addMockResponse({
  22. url: `/projects/${orgSlug}/${project.slug}/keys/`,
  23. body: [PROJECT_KEY],
  24. });
  25. MockApiClient.addMockResponse({
  26. url: `/projects/${orgSlug}/${project.slug}/issues/`,
  27. body: [],
  28. });
  29. MockApiClient.addMockResponse({
  30. url: `/organizations/${orgSlug}/sdks/`,
  31. body: {
  32. 'sentry.java.android.gradle-plugin': {
  33. canonical: 'maven:io.sentry:sentry',
  34. main_docs_url: 'https://docs.sentry.io/platforms/java',
  35. name: 'io.sentry:sentry',
  36. package_url: 'https://search.maven.org/artifact/io.sentry/sentry',
  37. repo_url: 'https://github.com/getsentry/sentry-java',
  38. version: '3.12.0',
  39. },
  40. },
  41. });
  42. if (project.slug !== 'javascript-react') {
  43. MockApiClient.addMockResponse({
  44. url: `/projects/${orgSlug}/${project.slug}/docs/${project.platform}/`,
  45. body: {html: ''},
  46. });
  47. }
  48. }
  49. describe('Onboarding Setup Docs', function () {
  50. it('does not render Product Selection', async function () {
  51. const {router, route, routerContext, organization, project} = initializeOrg({
  52. projects: [
  53. {
  54. ...initializeOrg().project,
  55. slug: 'python',
  56. platform: 'python',
  57. },
  58. ],
  59. });
  60. ProjectsStore.init();
  61. ProjectsStore.loadInitialData([project]);
  62. renderMockRequests({project, orgSlug: organization.slug});
  63. render(
  64. <OnboardingContextProvider>
  65. <SetupDocs
  66. active
  67. onComplete={() => {}}
  68. stepIndex={2}
  69. router={router}
  70. route={route}
  71. location={router.location}
  72. genSkipOnboardingLink={() => ''}
  73. orgId={organization.slug}
  74. search=""
  75. recentCreatedProject={project as OnboardingRecentCreatedProject}
  76. />
  77. </OnboardingContextProvider>,
  78. {
  79. context: routerContext,
  80. organization,
  81. }
  82. );
  83. expect(
  84. await screen.findByRole('heading', {name: 'Configure Python SDK'})
  85. ).toBeInTheDocument();
  86. expect(
  87. screen.queryByTestId(
  88. `product-${ProductSolution.ERROR_MONITORING}-${ProductSolution.PERFORMANCE_MONITORING}-${ProductSolution.SESSION_REPLAY}`
  89. )
  90. ).not.toBeInTheDocument();
  91. });
  92. it('renders SDK version from the sentry release registry', async function () {
  93. const {router, route, routerContext, organization, project} = initializeOrg({
  94. projects: [
  95. {
  96. ...initializeOrg().project,
  97. slug: 'java',
  98. platform: 'java',
  99. },
  100. ],
  101. });
  102. ProjectsStore.init();
  103. ProjectsStore.loadInitialData([project]);
  104. renderMockRequests({project, orgSlug: organization.slug});
  105. render(
  106. <OnboardingContextProvider>
  107. <SetupDocs
  108. active
  109. onComplete={() => {}}
  110. stepIndex={2}
  111. router={router}
  112. route={route}
  113. location={router.location}
  114. genSkipOnboardingLink={() => ''}
  115. orgId={organization.slug}
  116. search=""
  117. recentCreatedProject={project as OnboardingRecentCreatedProject}
  118. />
  119. </OnboardingContextProvider>,
  120. {
  121. context: routerContext,
  122. organization,
  123. }
  124. );
  125. expect(
  126. await screen.findByText(/id "io.sentry.jvm.gradle" version "3.12.0"/)
  127. ).toBeInTheDocument();
  128. });
  129. describe('renders Product Selection', function () {
  130. it('all products checked', async function () {
  131. const {router, route, routerContext, organization, project} = initializeOrg({
  132. router: {
  133. location: {
  134. query: {
  135. product: [
  136. ProductSolution.PERFORMANCE_MONITORING,
  137. ProductSolution.SESSION_REPLAY,
  138. ],
  139. },
  140. },
  141. },
  142. projects: [
  143. {
  144. ...initializeOrg().project,
  145. slug: 'javascript-react',
  146. platform: 'javascript-react',
  147. },
  148. ],
  149. });
  150. ProjectsStore.init();
  151. ProjectsStore.loadInitialData([project]);
  152. renderMockRequests({
  153. project,
  154. orgSlug: organization.slug,
  155. });
  156. render(
  157. <OnboardingContextProvider>
  158. <SetupDocs
  159. active
  160. onComplete={() => {}}
  161. stepIndex={2}
  162. router={router}
  163. route={route}
  164. location={router.location}
  165. genSkipOnboardingLink={() => ''}
  166. orgId={organization.slug}
  167. search=""
  168. recentCreatedProject={project as OnboardingRecentCreatedProject}
  169. />
  170. </OnboardingContextProvider>,
  171. {
  172. context: routerContext,
  173. organization,
  174. }
  175. );
  176. expect(
  177. await screen.findByRole('heading', {name: 'Configure React SDK'})
  178. ).toBeInTheDocument();
  179. const codeBlock = await screen.findByText(/import \* as Sentry/);
  180. expect(codeBlock).toHaveTextContent(/Performance Monitoring/);
  181. expect(codeBlock).toHaveTextContent(/Session Replay/);
  182. });
  183. it('only performance checked', async function () {
  184. const {router, route, routerContext, organization, project} = initializeOrg({
  185. router: {
  186. location: {
  187. query: {product: [ProductSolution.PERFORMANCE_MONITORING]},
  188. },
  189. },
  190. projects: [
  191. {
  192. ...initializeOrg().project,
  193. slug: 'javascript-react',
  194. platform: 'javascript-react',
  195. },
  196. ],
  197. });
  198. ProjectsStore.init();
  199. ProjectsStore.loadInitialData([project]);
  200. renderMockRequests({
  201. project,
  202. orgSlug: organization.slug,
  203. });
  204. render(
  205. <OnboardingContextProvider>
  206. <SetupDocs
  207. active
  208. onComplete={() => {}}
  209. stepIndex={2}
  210. router={router}
  211. route={route}
  212. location={router.location}
  213. genSkipOnboardingLink={() => ''}
  214. orgId={organization.slug}
  215. search=""
  216. recentCreatedProject={project as OnboardingRecentCreatedProject}
  217. />
  218. </OnboardingContextProvider>,
  219. {
  220. context: routerContext,
  221. organization,
  222. }
  223. );
  224. const codeBlock = await screen.findByText(/import \* as Sentry/);
  225. expect(codeBlock).toHaveTextContent(/Performance Monitoring/);
  226. expect(codeBlock).not.toHaveTextContent(/Session Replay/);
  227. });
  228. it('only session replay checked', async function () {
  229. const {router, route, routerContext, organization, project} = initializeOrg({
  230. router: {
  231. location: {
  232. query: {product: [ProductSolution.SESSION_REPLAY]},
  233. },
  234. },
  235. projects: [
  236. {
  237. ...initializeOrg().project,
  238. slug: 'javascript-react',
  239. platform: 'javascript-react',
  240. },
  241. ],
  242. });
  243. ProjectsStore.init();
  244. ProjectsStore.loadInitialData([project]);
  245. renderMockRequests({
  246. project,
  247. orgSlug: organization.slug,
  248. });
  249. render(
  250. <OnboardingContextProvider>
  251. <SetupDocs
  252. active
  253. onComplete={() => {}}
  254. stepIndex={2}
  255. router={router}
  256. route={route}
  257. location={router.location}
  258. genSkipOnboardingLink={() => ''}
  259. orgId={organization.slug}
  260. search=""
  261. recentCreatedProject={project as OnboardingRecentCreatedProject}
  262. />
  263. </OnboardingContextProvider>,
  264. {
  265. context: routerContext,
  266. organization,
  267. }
  268. );
  269. const codeBlock = await screen.findByText(/import \* as Sentry/);
  270. expect(codeBlock).toHaveTextContent(/Session Replay/);
  271. expect(codeBlock).not.toHaveTextContent(/Performance Monitoring/);
  272. });
  273. it('only error monitoring checked', async function () {
  274. const {router, route, routerContext, organization, project} = initializeOrg({
  275. router: {
  276. location: {
  277. query: {product: []},
  278. },
  279. },
  280. projects: [
  281. {
  282. ...initializeOrg().project,
  283. slug: 'javascript-react',
  284. platform: 'javascript-react',
  285. },
  286. ],
  287. });
  288. ProjectsStore.init();
  289. ProjectsStore.loadInitialData([project]);
  290. renderMockRequests({
  291. project,
  292. orgSlug: organization.slug,
  293. });
  294. render(
  295. <OnboardingContextProvider>
  296. <SetupDocs
  297. active
  298. onComplete={() => {}}
  299. stepIndex={2}
  300. router={router}
  301. route={route}
  302. location={router.location}
  303. genSkipOnboardingLink={() => ''}
  304. orgId={organization.slug}
  305. search=""
  306. recentCreatedProject={project as OnboardingRecentCreatedProject}
  307. />
  308. </OnboardingContextProvider>,
  309. {
  310. context: routerContext,
  311. organization,
  312. }
  313. );
  314. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  315. const codeBlock = await screen.findByText(/import \* as Sentry/);
  316. expect(codeBlock).not.toHaveTextContent(/Performance Monitoring/);
  317. expect(codeBlock).not.toHaveTextContent(/Session Replay/);
  318. });
  319. });
  320. describe('JS Loader Script', function () {
  321. it('renders Loader Script setup', async function () {
  322. const {router, route, routerContext, organization, project} = initializeOrg({
  323. router: {
  324. location: {
  325. query: {
  326. product: [
  327. ProductSolution.PERFORMANCE_MONITORING,
  328. ProductSolution.SESSION_REPLAY,
  329. ],
  330. },
  331. },
  332. },
  333. projects: [
  334. {
  335. ...initializeOrg().project,
  336. slug: 'javascript',
  337. platform: 'javascript',
  338. },
  339. ],
  340. });
  341. const updateLoaderMock = MockApiClient.addMockResponse({
  342. url: `/projects/${organization.slug}/${project.slug}/keys/${PROJECT_KEY.id}/`,
  343. method: 'PUT',
  344. body: PROJECT_KEY,
  345. });
  346. ProjectsStore.init();
  347. ProjectsStore.loadInitialData([project]);
  348. renderMockRequests({
  349. project,
  350. orgSlug: organization.slug,
  351. });
  352. const {rerender} = render(
  353. <OnboardingContextProvider>
  354. <SetupDocs
  355. active
  356. onComplete={() => {}}
  357. stepIndex={2}
  358. router={router}
  359. route={route}
  360. location={router.location}
  361. genSkipOnboardingLink={() => ''}
  362. orgId={organization.slug}
  363. search=""
  364. recentCreatedProject={project as OnboardingRecentCreatedProject}
  365. />
  366. </OnboardingContextProvider>,
  367. {
  368. context: routerContext,
  369. organization,
  370. }
  371. );
  372. expect(
  373. await screen.findByRole('heading', {name: 'Configure Browser JavaScript SDK'})
  374. ).toBeInTheDocument();
  375. expect(updateLoaderMock).toHaveBeenCalledTimes(1);
  376. expect(updateLoaderMock).toHaveBeenCalledWith(
  377. expect.any(String), // The URL
  378. {
  379. data: {
  380. dynamicSdkLoaderOptions: {
  381. hasDebug: false,
  382. hasPerformance: true,
  383. hasReplay: true,
  384. },
  385. },
  386. error: expect.any(Function),
  387. method: 'PUT',
  388. success: expect.any(Function),
  389. }
  390. );
  391. // update query in URL
  392. router.location.query = {
  393. product: [ProductSolution.SESSION_REPLAY],
  394. };
  395. rerender(
  396. <OnboardingContextProvider>
  397. <SetupDocs
  398. active
  399. onComplete={() => {}}
  400. stepIndex={2}
  401. router={router}
  402. route={route}
  403. location={router.location}
  404. genSkipOnboardingLink={() => ''}
  405. orgId={organization.slug}
  406. search=""
  407. recentCreatedProject={project as OnboardingRecentCreatedProject}
  408. />
  409. </OnboardingContextProvider>
  410. );
  411. expect(updateLoaderMock).toHaveBeenCalledTimes(2);
  412. expect(updateLoaderMock).toHaveBeenLastCalledWith(
  413. expect.any(String), // The URL
  414. {
  415. data: {
  416. dynamicSdkLoaderOptions: {
  417. hasDebug: false,
  418. hasPerformance: false,
  419. hasReplay: true,
  420. },
  421. },
  422. error: expect.any(Function),
  423. method: 'PUT',
  424. success: expect.any(Function),
  425. }
  426. );
  427. });
  428. });
  429. describe('special platforms', () => {
  430. it('renders platform other', async function () {
  431. const {router, route, routerContext, organization, project} = initializeOrg({
  432. projects: [
  433. {
  434. ...initializeOrg().project,
  435. slug: 'other',
  436. platform: 'other',
  437. },
  438. ],
  439. });
  440. ProjectsStore.init();
  441. ProjectsStore.loadInitialData([project]);
  442. renderMockRequests({project, orgSlug: organization.slug});
  443. render(
  444. <OnboardingContextProvider>
  445. <SetupDocs
  446. active
  447. onComplete={() => {}}
  448. stepIndex={2}
  449. router={router}
  450. route={route}
  451. location={router.location}
  452. genSkipOnboardingLink={() => ''}
  453. orgId={organization.slug}
  454. search=""
  455. recentCreatedProject={project as OnboardingRecentCreatedProject}
  456. />
  457. </OnboardingContextProvider>,
  458. {
  459. context: routerContext,
  460. organization,
  461. }
  462. );
  463. expect(
  464. await screen.findByRole('heading', {name: 'Configure Other SDK'})
  465. ).toBeInTheDocument();
  466. });
  467. });
  468. });