setupDocs.spec.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. import {Location} from 'history';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen} from 'sentry-test/reactTestingLibrary';
  4. import {PRODUCT} from 'sentry/components/onboarding/productSelection';
  5. import {ReactDocVariant} from 'sentry/data/platforms';
  6. import {PersistedStoreContext} from 'sentry/stores/persistedStore';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import {Organization, Project} from 'sentry/types';
  9. import SetupDocs from 'sentry/views/onboarding/setupDocs';
  10. const PROJECT_KEY = TestStubs.ProjectKeys()[0];
  11. function renderMockRequests({
  12. project,
  13. orgSlug,
  14. location,
  15. }: {
  16. orgSlug: Organization['slug'];
  17. project: Project;
  18. location?: Location;
  19. }) {
  20. MockApiClient.addMockResponse({
  21. url: `/projects/${orgSlug}/${project.slug}/`,
  22. body: project,
  23. });
  24. if (project.slug === 'javascript-browser') {
  25. MockApiClient.addMockResponse({
  26. url: `/projects/${orgSlug}/${project.slug}/keys/`,
  27. body: [PROJECT_KEY],
  28. });
  29. }
  30. MockApiClient.addMockResponse({
  31. url: `/projects/${orgSlug}/${project.slug}/issues/`,
  32. body: [],
  33. });
  34. if (project.slug === 'javascript-react') {
  35. const products = location?.query.product ?? [];
  36. if (
  37. products.includes(PRODUCT.PERFORMANCE_MONITORING) &&
  38. products.includes(PRODUCT.SESSION_REPLAY)
  39. ) {
  40. MockApiClient.addMockResponse({
  41. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringPerformanceAndReplay}/`,
  42. body: {html: ReactDocVariant.ErrorMonitoringPerformanceAndReplay},
  43. });
  44. } else if (products.includes(PRODUCT.PERFORMANCE_MONITORING)) {
  45. MockApiClient.addMockResponse({
  46. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringAndPerformance}/`,
  47. body: {html: ReactDocVariant.ErrorMonitoringAndPerformance},
  48. });
  49. } else if (products.includes(PRODUCT.SESSION_REPLAY)) {
  50. MockApiClient.addMockResponse({
  51. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringAndSessionReplay}/`,
  52. body: {html: ReactDocVariant.ErrorMonitoringAndSessionReplay},
  53. });
  54. } else {
  55. MockApiClient.addMockResponse({
  56. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoring}/`,
  57. body: {html: ReactDocVariant.ErrorMonitoring},
  58. });
  59. }
  60. } else {
  61. MockApiClient.addMockResponse({
  62. url: `/projects/${orgSlug}/${project.slug}/docs/${project.platform}/`,
  63. body: {html: ''},
  64. });
  65. }
  66. }
  67. describe('Onboarding Setup Docs', function () {
  68. it('does not render Product Selection', async function () {
  69. const {router, route, routerContext, organization, project} = initializeOrg({
  70. ...initializeOrg(),
  71. organization: {
  72. ...initializeOrg().organization,
  73. features: [
  74. 'onboarding-remove-multiselect-platform',
  75. 'onboarding-docs-with-product-selection',
  76. ],
  77. },
  78. projects: [
  79. {
  80. ...initializeOrg().project,
  81. slug: 'python',
  82. platform: 'python',
  83. },
  84. ],
  85. });
  86. ProjectsStore.init();
  87. ProjectsStore.loadInitialData([project]);
  88. renderMockRequests({project, orgSlug: organization.slug});
  89. render(
  90. <PersistedStoreContext.Provider
  91. value={[
  92. {
  93. onboarding: {
  94. selectedPlatforms: ['python'],
  95. platformToProjectIdMap: {
  96. python: 'python',
  97. },
  98. },
  99. },
  100. jest.fn(),
  101. ]}
  102. >
  103. <SetupDocs
  104. active
  105. onComplete={() => {}}
  106. stepIndex={2}
  107. router={router}
  108. route={route}
  109. location={router.location}
  110. genSkipOnboardingLink={() => ''}
  111. orgId={organization.slug}
  112. jumpToSetupProject={() => {}}
  113. search=""
  114. />
  115. </PersistedStoreContext.Provider>,
  116. {
  117. context: routerContext,
  118. organization,
  119. }
  120. );
  121. expect(
  122. await screen.findByRole('heading', {name: 'Configure Python SDK'})
  123. ).toBeInTheDocument();
  124. expect(
  125. screen.queryByTestId(
  126. `product-${PRODUCT.ERROR_MONITORING}-${PRODUCT.PERFORMANCE_MONITORING}-${PRODUCT.SESSION_REPLAY}`
  127. )
  128. ).not.toBeInTheDocument();
  129. });
  130. describe('renders Product Selection', function () {
  131. it('all products checked', async function () {
  132. const {router, route, routerContext, organization, project} = initializeOrg({
  133. ...initializeOrg(),
  134. organization: {
  135. ...initializeOrg().organization,
  136. features: [
  137. 'onboarding-remove-multiselect-platform',
  138. 'onboarding-docs-with-product-selection',
  139. ],
  140. },
  141. router: {
  142. location: {
  143. query: {product: [PRODUCT.PERFORMANCE_MONITORING, PRODUCT.SESSION_REPLAY]},
  144. },
  145. },
  146. projects: [
  147. {
  148. ...initializeOrg().project,
  149. slug: 'javascript-react',
  150. platform: 'javascript-react',
  151. },
  152. ],
  153. });
  154. ProjectsStore.init();
  155. ProjectsStore.loadInitialData([project]);
  156. renderMockRequests({
  157. project,
  158. orgSlug: organization.slug,
  159. location: router.location,
  160. });
  161. render(
  162. <PersistedStoreContext.Provider
  163. value={[
  164. {
  165. onboarding: {
  166. selectedPlatforms: ['javascript-react'],
  167. platformToProjectIdMap: {
  168. 'javascript-react': 'javascript-react',
  169. },
  170. },
  171. },
  172. jest.fn(),
  173. ]}
  174. >
  175. <SetupDocs
  176. active
  177. onComplete={() => {}}
  178. stepIndex={2}
  179. router={router}
  180. route={route}
  181. location={router.location}
  182. genSkipOnboardingLink={() => ''}
  183. orgId={organization.slug}
  184. jumpToSetupProject={() => {}}
  185. search=""
  186. />
  187. </PersistedStoreContext.Provider>,
  188. {
  189. context: routerContext,
  190. organization,
  191. }
  192. );
  193. expect(
  194. await screen.findByRole('heading', {name: 'Configure React SDK'})
  195. ).toBeInTheDocument();
  196. // Render variation of docs - default (all checked)
  197. expect(
  198. await screen.findByText(ReactDocVariant.ErrorMonitoringPerformanceAndReplay)
  199. ).toBeInTheDocument();
  200. });
  201. it('only performance checked', async function () {
  202. const {router, route, routerContext, organization, project} = initializeOrg({
  203. ...initializeOrg(),
  204. organization: {
  205. ...initializeOrg().organization,
  206. features: [
  207. 'onboarding-remove-multiselect-platform',
  208. 'onboarding-docs-with-product-selection',
  209. ],
  210. },
  211. router: {
  212. location: {
  213. query: {product: [PRODUCT.PERFORMANCE_MONITORING]},
  214. },
  215. },
  216. projects: [
  217. {
  218. ...initializeOrg().project,
  219. slug: 'javascript-react',
  220. platform: 'javascript-react',
  221. },
  222. ],
  223. });
  224. ProjectsStore.init();
  225. ProjectsStore.loadInitialData([project]);
  226. renderMockRequests({
  227. project,
  228. orgSlug: organization.slug,
  229. location: router.location,
  230. });
  231. render(
  232. <PersistedStoreContext.Provider
  233. value={[
  234. {
  235. onboarding: {
  236. selectedPlatforms: ['javascript-react'],
  237. platformToProjectIdMap: {
  238. 'javascript-react': 'javascript-react',
  239. },
  240. },
  241. },
  242. jest.fn(),
  243. ]}
  244. >
  245. <SetupDocs
  246. active
  247. onComplete={() => {}}
  248. stepIndex={2}
  249. router={router}
  250. route={route}
  251. location={router.location}
  252. genSkipOnboardingLink={() => ''}
  253. orgId={organization.slug}
  254. jumpToSetupProject={() => {}}
  255. search=""
  256. />
  257. </PersistedStoreContext.Provider>,
  258. {
  259. context: routerContext,
  260. organization,
  261. }
  262. );
  263. // Render variation of docs - error monitoring and performance doc
  264. expect(
  265. await screen.findByText(ReactDocVariant.ErrorMonitoringAndPerformance)
  266. ).toBeInTheDocument();
  267. });
  268. it('only session replay checked', async function () {
  269. const {router, route, routerContext, organization, project} = initializeOrg({
  270. ...initializeOrg(),
  271. organization: {
  272. ...initializeOrg().organization,
  273. features: [
  274. 'onboarding-remove-multiselect-platform',
  275. 'onboarding-docs-with-product-selection',
  276. ],
  277. },
  278. router: {
  279. location: {
  280. query: {product: [PRODUCT.SESSION_REPLAY]},
  281. },
  282. },
  283. projects: [
  284. {
  285. ...initializeOrg().project,
  286. slug: 'javascript-react',
  287. platform: 'javascript-react',
  288. },
  289. ],
  290. });
  291. ProjectsStore.init();
  292. ProjectsStore.loadInitialData([project]);
  293. renderMockRequests({
  294. project,
  295. orgSlug: organization.slug,
  296. location: router.location,
  297. });
  298. render(
  299. <PersistedStoreContext.Provider
  300. value={[
  301. {
  302. onboarding: {
  303. selectedPlatforms: ['javascript-react'],
  304. platformToProjectIdMap: {
  305. 'javascript-react': 'javascript-react',
  306. },
  307. },
  308. },
  309. jest.fn(),
  310. ]}
  311. >
  312. <SetupDocs
  313. active
  314. onComplete={() => {}}
  315. stepIndex={2}
  316. router={router}
  317. route={route}
  318. location={router.location}
  319. genSkipOnboardingLink={() => ''}
  320. orgId={organization.slug}
  321. jumpToSetupProject={() => {}}
  322. search=""
  323. />
  324. </PersistedStoreContext.Provider>,
  325. {
  326. context: routerContext,
  327. organization,
  328. }
  329. );
  330. // Render variation of docs - error monitoring and replay doc
  331. expect(
  332. await screen.findByText(ReactDocVariant.ErrorMonitoringAndSessionReplay)
  333. ).toBeInTheDocument();
  334. });
  335. it('only error monitoring checked', async function () {
  336. const {router, route, routerContext, organization, project} = initializeOrg({
  337. ...initializeOrg(),
  338. organization: {
  339. ...initializeOrg().organization,
  340. features: [
  341. 'onboarding-remove-multiselect-platform',
  342. 'onboarding-docs-with-product-selection',
  343. ],
  344. },
  345. router: {
  346. location: {
  347. query: {product: []},
  348. },
  349. },
  350. projects: [
  351. {
  352. ...initializeOrg().project,
  353. slug: 'javascript-react',
  354. platform: 'javascript-react',
  355. },
  356. ],
  357. });
  358. ProjectsStore.init();
  359. ProjectsStore.loadInitialData([project]);
  360. renderMockRequests({
  361. project,
  362. orgSlug: organization.slug,
  363. location: router.location,
  364. });
  365. render(
  366. <PersistedStoreContext.Provider
  367. value={[
  368. {
  369. onboarding: {
  370. selectedPlatforms: ['javascript-react'],
  371. platformToProjectIdMap: {
  372. 'javascript-react': 'javascript-react',
  373. },
  374. },
  375. },
  376. jest.fn(),
  377. ]}
  378. >
  379. <SetupDocs
  380. active
  381. onComplete={() => {}}
  382. stepIndex={2}
  383. router={router}
  384. route={route}
  385. location={router.location}
  386. genSkipOnboardingLink={() => ''}
  387. orgId={organization.slug}
  388. jumpToSetupProject={() => {}}
  389. search=""
  390. />
  391. </PersistedStoreContext.Provider>,
  392. {
  393. context: routerContext,
  394. organization,
  395. }
  396. );
  397. // Render variation of docs - error monitoring doc
  398. expect(
  399. await screen.findByText(ReactDocVariant.ErrorMonitoring)
  400. ).toBeInTheDocument();
  401. });
  402. });
  403. describe('JS Loader Script', function () {
  404. it('renders Loader Script setup', async function () {
  405. const {router, route, routerContext, organization, project} = initializeOrg({
  406. ...initializeOrg(),
  407. organization: {
  408. ...initializeOrg().organization,
  409. features: [
  410. 'onboarding-remove-multiselect-platform',
  411. 'onboarding-docs-with-product-selection',
  412. ],
  413. },
  414. router: {
  415. location: {
  416. query: {product: [PRODUCT.PERFORMANCE_MONITORING, PRODUCT.SESSION_REPLAY]},
  417. },
  418. },
  419. projects: [
  420. {
  421. ...initializeOrg().project,
  422. slug: 'javascript-browser',
  423. platform: 'javascript',
  424. },
  425. ],
  426. });
  427. const updateLoaderMock = MockApiClient.addMockResponse({
  428. url: `/projects/${organization.slug}/${project.slug}/keys/${PROJECT_KEY.id}/`,
  429. method: 'PUT',
  430. body: PROJECT_KEY,
  431. });
  432. ProjectsStore.init();
  433. ProjectsStore.loadInitialData([project]);
  434. renderMockRequests({
  435. project,
  436. orgSlug: organization.slug,
  437. location: router.location,
  438. });
  439. const {rerender} = render(
  440. <PersistedStoreContext.Provider
  441. value={[
  442. {
  443. onboarding: {
  444. selectedPlatforms: ['javascript'],
  445. platformToProjectIdMap: {
  446. javascript: 'javascript-browser',
  447. },
  448. },
  449. },
  450. jest.fn(),
  451. ]}
  452. >
  453. <SetupDocs
  454. active
  455. onComplete={() => {}}
  456. stepIndex={2}
  457. router={router}
  458. route={route}
  459. location={router.location}
  460. genSkipOnboardingLink={() => ''}
  461. orgId={organization.slug}
  462. jumpToSetupProject={() => {}}
  463. search=""
  464. />
  465. </PersistedStoreContext.Provider>,
  466. {
  467. context: routerContext,
  468. organization,
  469. }
  470. );
  471. expect(
  472. await screen.findByRole('heading', {name: 'Configure JavaScript SDK'})
  473. ).toBeInTheDocument();
  474. expect(updateLoaderMock).toHaveBeenCalledTimes(1);
  475. expect(updateLoaderMock).toHaveBeenCalledWith(
  476. expect.any(String), // The URL
  477. {
  478. data: {
  479. dynamicSdkLoaderOptions: {
  480. hasDebug: false,
  481. hasPerformance: true,
  482. hasReplay: true,
  483. },
  484. },
  485. error: expect.any(Function),
  486. method: 'PUT',
  487. success: expect.any(Function),
  488. }
  489. );
  490. // update query in URL
  491. router.location.query = {
  492. product: [PRODUCT.SESSION_REPLAY],
  493. };
  494. rerender(
  495. <PersistedStoreContext.Provider
  496. value={[
  497. {
  498. onboarding: {
  499. selectedPlatforms: ['javascript'],
  500. platformToProjectIdMap: {
  501. javascript: 'javascript-browser',
  502. },
  503. },
  504. },
  505. jest.fn(),
  506. ]}
  507. >
  508. <SetupDocs
  509. active
  510. onComplete={() => {}}
  511. stepIndex={2}
  512. router={router}
  513. route={route}
  514. location={router.location}
  515. genSkipOnboardingLink={() => ''}
  516. orgId={organization.slug}
  517. jumpToSetupProject={() => {}}
  518. search=""
  519. />
  520. </PersistedStoreContext.Provider>
  521. );
  522. expect(updateLoaderMock).toHaveBeenCalledTimes(2);
  523. expect(updateLoaderMock).toHaveBeenLastCalledWith(
  524. expect.any(String), // The URL
  525. {
  526. data: {
  527. dynamicSdkLoaderOptions: {
  528. hasDebug: false,
  529. hasPerformance: false,
  530. hasReplay: true,
  531. },
  532. },
  533. error: expect.any(Function),
  534. method: 'PUT',
  535. success: expect.any(Function),
  536. }
  537. );
  538. });
  539. });
  540. });