PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/js/spec/views/dashboardsV2/widgetCard.spec.tsx

https://github.com/getsentry/sentry
TypeScript | 786 lines | 735 code | 46 blank | 5 comment | 10 complexity | d200041c164834dcde05a71e91c7bec0 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {mountGlobalModal} from 'sentry-test/modal';
  3. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  4. import * as modal from 'sentry/actionCreators/modal';
  5. import {Client} from 'sentry/api';
  6. import SimpleTableChart from 'sentry/components/charts/simpleTableChart';
  7. import {DisplayType, Widget, WidgetType} from 'sentry/views/dashboardsV2/types';
  8. import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
  9. import ReleaseWidgetQueries from 'sentry/views/dashboardsV2/widgetCard/releaseWidgetQueries';
  10. jest.mock('sentry/components/charts/simpleTableChart');
  11. jest.mock('sentry/views/dashboardsV2/widgetCard/releaseWidgetQueries');
  12. describe('Dashboards > WidgetCard', function () {
  13. const {router, organization, routerContext} = initializeOrg({
  14. organization: TestStubs.Organization({
  15. features: ['dashboards-edit', 'discover-basic'],
  16. projects: [TestStubs.Project()],
  17. }),
  18. router: {orgId: 'orgId'},
  19. } as Parameters<typeof initializeOrg>[0]);
  20. const multipleQueryWidget: Widget = {
  21. title: 'Errors',
  22. interval: '5m',
  23. displayType: DisplayType.LINE,
  24. widgetType: WidgetType.DISCOVER,
  25. queries: [
  26. {
  27. conditions: 'event.type:error',
  28. fields: ['count()', 'failure_count()'],
  29. aggregates: ['count()', 'failure_count()'],
  30. columns: [],
  31. name: 'errors',
  32. orderby: '',
  33. },
  34. {
  35. conditions: 'event.type:default',
  36. fields: ['count()', 'failure_count()'],
  37. aggregates: ['count()', 'failure_count()'],
  38. columns: [],
  39. name: 'default',
  40. orderby: '',
  41. },
  42. ],
  43. };
  44. const selection = {
  45. projects: [1],
  46. environments: ['prod'],
  47. datetime: {
  48. period: '14d',
  49. start: null,
  50. end: null,
  51. utc: false,
  52. },
  53. };
  54. const api = new Client();
  55. let eventsv2Mock, eventsMock;
  56. beforeEach(function () {
  57. MockApiClient.addMockResponse({
  58. url: '/organizations/org-slug/events-stats/',
  59. body: [],
  60. });
  61. MockApiClient.addMockResponse({
  62. url: '/organizations/org-slug/events-geo/',
  63. body: [],
  64. });
  65. eventsv2Mock = MockApiClient.addMockResponse({
  66. url: '/organizations/org-slug/eventsv2/',
  67. body: {
  68. meta: {title: 'string'},
  69. data: [{title: 'title'}],
  70. },
  71. });
  72. eventsMock = MockApiClient.addMockResponse({
  73. url: '/organizations/org-slug/events/',
  74. body: {
  75. meta: {fields: {title: 'string'}},
  76. data: [{title: 'title'}],
  77. },
  78. });
  79. });
  80. afterEach(function () {
  81. MockApiClient.clearMockResponses();
  82. });
  83. it('renders with Open in Discover button and opens the Query Selector Modal when clicked', async function () {
  84. const spy = jest.spyOn(modal, 'openDashboardWidgetQuerySelectorModal');
  85. render(
  86. <WidgetCard
  87. api={api}
  88. organization={organization}
  89. widget={multipleQueryWidget}
  90. selection={selection}
  91. isEditing={false}
  92. onDelete={() => undefined}
  93. onEdit={() => undefined}
  94. onDuplicate={() => undefined}
  95. renderErrorMessage={() => undefined}
  96. isSorting={false}
  97. currentWidgetDragging={false}
  98. showContextMenu
  99. widgetLimitReached={false}
  100. />
  101. );
  102. userEvent.click(await screen.findByLabelText('Widget actions'));
  103. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  104. userEvent.click(screen.getByText('Open in Discover'));
  105. expect(spy).toHaveBeenCalledWith({
  106. organization,
  107. widget: multipleQueryWidget,
  108. });
  109. });
  110. it('renders with Open in Discover button and opens in Discover when clicked', async function () {
  111. render(
  112. <WidgetCard
  113. api={api}
  114. organization={organization}
  115. widget={{...multipleQueryWidget, queries: [multipleQueryWidget.queries[0]]}}
  116. selection={selection}
  117. isEditing={false}
  118. onDelete={() => undefined}
  119. onEdit={() => undefined}
  120. onDuplicate={() => undefined}
  121. renderErrorMessage={() => undefined}
  122. isSorting={false}
  123. currentWidgetDragging={false}
  124. showContextMenu
  125. widgetLimitReached={false}
  126. />,
  127. {context: routerContext}
  128. );
  129. userEvent.click(await screen.findByLabelText('Widget actions'));
  130. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  131. userEvent.click(screen.getByText('Open in Discover'));
  132. expect(router.push).toHaveBeenCalledWith(
  133. '/organizations/org-slug/discover/results/?environment=prod&field=count%28%29&field=failure_count%28%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29&yAxis=failure_count%28%29'
  134. );
  135. });
  136. it('Opens in Discover with World Map', async function () {
  137. render(
  138. <WidgetCard
  139. api={api}
  140. organization={organization}
  141. widget={{
  142. ...multipleQueryWidget,
  143. displayType: DisplayType.WORLD_MAP,
  144. queries: [
  145. {
  146. ...multipleQueryWidget.queries[0],
  147. fields: ['count()'],
  148. aggregates: ['count()'],
  149. columns: [],
  150. },
  151. ],
  152. }}
  153. selection={selection}
  154. isEditing={false}
  155. onDelete={() => undefined}
  156. onEdit={() => undefined}
  157. onDuplicate={() => undefined}
  158. renderErrorMessage={() => undefined}
  159. isSorting={false}
  160. currentWidgetDragging={false}
  161. showContextMenu
  162. widgetLimitReached={false}
  163. />,
  164. {context: routerContext}
  165. );
  166. userEvent.click(await screen.findByLabelText('Widget actions'));
  167. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  168. userEvent.click(screen.getByText('Open in Discover'));
  169. expect(router.push).toHaveBeenCalledWith(
  170. '/organizations/org-slug/discover/results/?display=worldmap&environment=prod&field=geo.country_code&field=count%28%29&name=Errors&project=1&query=event.type%3Aerror%20has%3Ageo.country_code&statsPeriod=14d&yAxis=count%28%29'
  171. );
  172. });
  173. it('Opens in Discover with prepended fields pulled from equations', async function () {
  174. render(
  175. <WidgetCard
  176. api={api}
  177. organization={organization}
  178. widget={{
  179. ...multipleQueryWidget,
  180. queries: [
  181. {
  182. ...multipleQueryWidget.queries[0],
  183. fields: [
  184. 'equation|(count() + failure_count()) / count_if(transaction.duration,equals,300)',
  185. ],
  186. columns: [],
  187. aggregates: [
  188. 'equation|(count() + failure_count()) / count_if(transaction.duration,equals,300)',
  189. ],
  190. },
  191. ],
  192. }}
  193. selection={selection}
  194. isEditing={false}
  195. onDelete={() => undefined}
  196. onEdit={() => undefined}
  197. onDuplicate={() => undefined}
  198. renderErrorMessage={() => undefined}
  199. isSorting={false}
  200. currentWidgetDragging={false}
  201. showContextMenu
  202. widgetLimitReached={false}
  203. />,
  204. {context: routerContext}
  205. );
  206. userEvent.click(await screen.findByLabelText('Widget actions'));
  207. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  208. userEvent.click(screen.getByText('Open in Discover'));
  209. expect(router.push).toHaveBeenCalledWith(
  210. '/organizations/org-slug/discover/results/?environment=prod&field=count_if%28transaction.duration%2Cequals%2C300%29&field=failure_count%28%29&field=count%28%29&field=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29'
  211. );
  212. });
  213. it('Opens in Discover with Top N', async function () {
  214. render(
  215. <WidgetCard
  216. api={api}
  217. organization={organization}
  218. widget={{
  219. ...multipleQueryWidget,
  220. displayType: DisplayType.TOP_N,
  221. queries: [
  222. {
  223. ...multipleQueryWidget.queries[0],
  224. fields: ['transaction', 'count()'],
  225. columns: ['transaction'],
  226. aggregates: ['count()'],
  227. },
  228. ],
  229. }}
  230. selection={selection}
  231. isEditing={false}
  232. onDelete={() => undefined}
  233. onEdit={() => undefined}
  234. onDuplicate={() => undefined}
  235. renderErrorMessage={() => undefined}
  236. isSorting={false}
  237. currentWidgetDragging={false}
  238. showContextMenu
  239. widgetLimitReached={false}
  240. />,
  241. {context: routerContext}
  242. );
  243. userEvent.click(await screen.findByLabelText('Widget actions'));
  244. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  245. userEvent.click(screen.getByText('Open in Discover'));
  246. expect(router.push).toHaveBeenCalledWith(
  247. '/organizations/org-slug/discover/results/?display=top5&environment=prod&field=transaction&field=count%28%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29'
  248. );
  249. });
  250. it('calls onDuplicate when Duplicate Widget is clicked', async function () {
  251. const mock = jest.fn();
  252. render(
  253. <WidgetCard
  254. api={api}
  255. organization={organization}
  256. widget={{
  257. ...multipleQueryWidget,
  258. displayType: DisplayType.WORLD_MAP,
  259. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  260. }}
  261. selection={selection}
  262. isEditing={false}
  263. onDelete={() => undefined}
  264. onEdit={() => undefined}
  265. onDuplicate={mock}
  266. renderErrorMessage={() => undefined}
  267. isSorting={false}
  268. currentWidgetDragging={false}
  269. showContextMenu
  270. widgetLimitReached={false}
  271. />
  272. );
  273. userEvent.click(await screen.findByLabelText('Widget actions'));
  274. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  275. userEvent.click(screen.getByText('Duplicate Widget'));
  276. expect(mock).toHaveBeenCalledTimes(1);
  277. });
  278. it('does not add duplicate widgets if max widget is reached', async function () {
  279. const mock = jest.fn();
  280. render(
  281. <WidgetCard
  282. api={api}
  283. organization={organization}
  284. widget={{
  285. ...multipleQueryWidget,
  286. displayType: DisplayType.WORLD_MAP,
  287. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  288. }}
  289. selection={selection}
  290. isEditing={false}
  291. onDelete={() => undefined}
  292. onEdit={() => undefined}
  293. onDuplicate={mock}
  294. renderErrorMessage={() => undefined}
  295. isSorting={false}
  296. currentWidgetDragging={false}
  297. showContextMenu
  298. widgetLimitReached
  299. />
  300. );
  301. userEvent.click(await screen.findByLabelText('Widget actions'));
  302. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  303. userEvent.click(screen.getByText('Duplicate Widget'));
  304. expect(mock).toHaveBeenCalledTimes(0);
  305. });
  306. it('calls onEdit when Edit Widget is clicked', async function () {
  307. const mock = jest.fn();
  308. render(
  309. <WidgetCard
  310. api={api}
  311. organization={organization}
  312. widget={{
  313. ...multipleQueryWidget,
  314. displayType: DisplayType.WORLD_MAP,
  315. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  316. }}
  317. selection={selection}
  318. isEditing={false}
  319. onDelete={() => undefined}
  320. onEdit={mock}
  321. onDuplicate={() => undefined}
  322. renderErrorMessage={() => undefined}
  323. isSorting={false}
  324. currentWidgetDragging={false}
  325. showContextMenu
  326. widgetLimitReached={false}
  327. />
  328. );
  329. userEvent.click(await screen.findByLabelText('Widget actions'));
  330. expect(screen.getByText('Edit Widget')).toBeInTheDocument();
  331. userEvent.click(screen.getByText('Edit Widget'));
  332. expect(mock).toHaveBeenCalledTimes(1);
  333. });
  334. it('renders delete widget option', async function () {
  335. const mock = jest.fn();
  336. render(
  337. <WidgetCard
  338. api={api}
  339. organization={organization}
  340. widget={{
  341. ...multipleQueryWidget,
  342. displayType: DisplayType.WORLD_MAP,
  343. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  344. }}
  345. selection={selection}
  346. isEditing={false}
  347. onDelete={mock}
  348. onEdit={() => undefined}
  349. onDuplicate={() => undefined}
  350. renderErrorMessage={() => undefined}
  351. isSorting={false}
  352. currentWidgetDragging={false}
  353. showContextMenu
  354. widgetLimitReached={false}
  355. />
  356. );
  357. userEvent.click(await screen.findByLabelText('Widget actions'));
  358. expect(screen.getByText('Delete Widget')).toBeInTheDocument();
  359. userEvent.click(screen.getByText('Delete Widget'));
  360. // Confirm Modal
  361. await mountGlobalModal();
  362. await screen.findByRole('dialog');
  363. userEvent.click(screen.getByTestId('confirm-button'));
  364. expect(mock).toHaveBeenCalled();
  365. });
  366. it('calls eventsV2 with a limit of 20 items', async function () {
  367. const mock = jest.fn();
  368. render(
  369. <WidgetCard
  370. api={api}
  371. organization={organization}
  372. widget={{
  373. ...multipleQueryWidget,
  374. displayType: DisplayType.TABLE,
  375. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  376. }}
  377. selection={selection}
  378. isEditing={false}
  379. onDelete={mock}
  380. onEdit={() => undefined}
  381. onDuplicate={() => undefined}
  382. renderErrorMessage={() => undefined}
  383. isSorting={false}
  384. currentWidgetDragging={false}
  385. showContextMenu
  386. widgetLimitReached={false}
  387. tableItemLimit={20}
  388. />
  389. );
  390. await tick();
  391. expect(eventsv2Mock).toHaveBeenCalledWith(
  392. '/organizations/org-slug/eventsv2/',
  393. expect.objectContaining({
  394. query: expect.objectContaining({
  395. per_page: 20,
  396. }),
  397. })
  398. );
  399. });
  400. it('calls eventsV2 with a default limit of 5 items', async function () {
  401. const mock = jest.fn();
  402. render(
  403. <WidgetCard
  404. api={api}
  405. organization={organization}
  406. widget={{
  407. ...multipleQueryWidget,
  408. displayType: DisplayType.TABLE,
  409. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  410. }}
  411. selection={selection}
  412. isEditing={false}
  413. onDelete={mock}
  414. onEdit={() => undefined}
  415. onDuplicate={() => undefined}
  416. renderErrorMessage={() => undefined}
  417. isSorting={false}
  418. currentWidgetDragging={false}
  419. showContextMenu
  420. widgetLimitReached={false}
  421. />
  422. );
  423. await tick();
  424. expect(eventsv2Mock).toHaveBeenCalledWith(
  425. '/organizations/org-slug/eventsv2/',
  426. expect.objectContaining({
  427. query: expect.objectContaining({
  428. per_page: 5,
  429. }),
  430. })
  431. );
  432. });
  433. it('has sticky table headers', async function () {
  434. const tableWidget: Widget = {
  435. title: 'Table Widget',
  436. interval: '5m',
  437. displayType: DisplayType.TABLE,
  438. widgetType: WidgetType.DISCOVER,
  439. queries: [
  440. {
  441. conditions: '',
  442. fields: ['transaction', 'count()'],
  443. columns: ['transaction'],
  444. aggregates: ['count()'],
  445. name: 'Table',
  446. orderby: '',
  447. },
  448. ],
  449. };
  450. render(
  451. <WidgetCard
  452. api={api}
  453. organization={organization}
  454. widget={tableWidget}
  455. selection={selection}
  456. isEditing={false}
  457. onDelete={() => undefined}
  458. onEdit={() => undefined}
  459. onDuplicate={() => undefined}
  460. renderErrorMessage={() => undefined}
  461. isSorting={false}
  462. currentWidgetDragging={false}
  463. showContextMenu
  464. widgetLimitReached={false}
  465. tableItemLimit={20}
  466. />
  467. );
  468. await tick();
  469. expect(SimpleTableChart).toHaveBeenCalledWith(
  470. expect.objectContaining({stickyHeaders: true}),
  471. expect.anything()
  472. );
  473. });
  474. it('calls release queries', function () {
  475. const widget: Widget = {
  476. title: 'Release Widget',
  477. interval: '5m',
  478. displayType: DisplayType.LINE,
  479. widgetType: WidgetType.RELEASE,
  480. queries: [],
  481. };
  482. render(
  483. <WidgetCard
  484. api={api}
  485. organization={organization}
  486. widget={widget}
  487. selection={selection}
  488. isEditing={false}
  489. onDelete={() => undefined}
  490. onEdit={() => undefined}
  491. onDuplicate={() => undefined}
  492. renderErrorMessage={() => undefined}
  493. isSorting={false}
  494. currentWidgetDragging={false}
  495. showContextMenu
  496. widgetLimitReached={false}
  497. tableItemLimit={20}
  498. />
  499. );
  500. expect(ReleaseWidgetQueries).toHaveBeenCalledTimes(1);
  501. });
  502. it('opens the widget viewer modal when a widget has no id', async () => {
  503. const widget: Widget = {
  504. title: 'Widget',
  505. interval: '5m',
  506. displayType: DisplayType.LINE,
  507. widgetType: WidgetType.DISCOVER,
  508. queries: [],
  509. };
  510. render(
  511. <WidgetCard
  512. api={api}
  513. organization={organization}
  514. widget={widget}
  515. selection={selection}
  516. isEditing={false}
  517. onDelete={() => undefined}
  518. onEdit={() => undefined}
  519. onDuplicate={() => undefined}
  520. renderErrorMessage={() => undefined}
  521. isSorting={false}
  522. currentWidgetDragging={false}
  523. showContextMenu
  524. widgetLimitReached={false}
  525. showWidgetViewerButton
  526. index="10"
  527. isPreview
  528. />,
  529. {context: routerContext}
  530. );
  531. userEvent.click(await screen.findByLabelText('Open Widget Viewer'));
  532. expect(router.push).toHaveBeenCalledWith(
  533. expect.objectContaining({pathname: '/mock-pathname/widget/10/'})
  534. );
  535. });
  536. it('renders stored data disclaimer', async function () {
  537. MockApiClient.addMockResponse({
  538. url: '/organizations/org-slug/eventsv2/',
  539. body: {
  540. meta: {title: 'string', isMetricsData: false},
  541. data: [{title: 'title'}],
  542. },
  543. });
  544. render(
  545. <WidgetCard
  546. api={api}
  547. organization={{
  548. ...organization,
  549. features: [...organization.features, 'dashboards-mep'],
  550. }}
  551. widget={{
  552. ...multipleQueryWidget,
  553. displayType: DisplayType.TABLE,
  554. queries: [{...multipleQueryWidget.queries[0]}],
  555. }}
  556. selection={selection}
  557. isEditing={false}
  558. onDelete={() => undefined}
  559. onEdit={() => undefined}
  560. onDuplicate={() => undefined}
  561. renderErrorMessage={() => undefined}
  562. isSorting={false}
  563. currentWidgetDragging={false}
  564. showContextMenu
  565. widgetLimitReached={false}
  566. showStoredAlert
  567. />,
  568. {context: routerContext}
  569. );
  570. await waitFor(() => {
  571. // Badge in the widget header
  572. expect(screen.getByText('Stored')).toBeInTheDocument();
  573. });
  574. await waitFor(() => {
  575. expect(
  576. // Alert below the widget
  577. screen.getByText(/we've automatically adjusted your results/i)
  578. ).toBeInTheDocument();
  579. });
  580. });
  581. describe('using events endpoint', () => {
  582. const organizationWithFlag = {
  583. ...organization,
  584. features: [...organization.features, 'discover-frontend-use-events-endpoint'],
  585. };
  586. it('calls eventsV2 with a limit of 20 items', async function () {
  587. const mock = jest.fn();
  588. render(
  589. <WidgetCard
  590. api={api}
  591. organization={organizationWithFlag}
  592. widget={{
  593. ...multipleQueryWidget,
  594. displayType: DisplayType.TABLE,
  595. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  596. }}
  597. selection={selection}
  598. isEditing={false}
  599. onDelete={mock}
  600. onEdit={() => undefined}
  601. onDuplicate={() => undefined}
  602. renderErrorMessage={() => undefined}
  603. isSorting={false}
  604. currentWidgetDragging={false}
  605. showContextMenu
  606. widgetLimitReached={false}
  607. tableItemLimit={20}
  608. />
  609. );
  610. await tick();
  611. expect(eventsMock).toHaveBeenCalledWith(
  612. '/organizations/org-slug/events/',
  613. expect.objectContaining({
  614. query: expect.objectContaining({
  615. per_page: 20,
  616. }),
  617. })
  618. );
  619. });
  620. it('calls eventsV2 with a default limit of 5 items', async function () {
  621. const mock = jest.fn();
  622. render(
  623. <WidgetCard
  624. api={api}
  625. organization={organizationWithFlag}
  626. widget={{
  627. ...multipleQueryWidget,
  628. displayType: DisplayType.TABLE,
  629. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  630. }}
  631. selection={selection}
  632. isEditing={false}
  633. onDelete={mock}
  634. onEdit={() => undefined}
  635. onDuplicate={() => undefined}
  636. renderErrorMessage={() => undefined}
  637. isSorting={false}
  638. currentWidgetDragging={false}
  639. showContextMenu
  640. widgetLimitReached={false}
  641. />
  642. );
  643. await tick();
  644. expect(eventsMock).toHaveBeenCalledWith(
  645. '/organizations/org-slug/events/',
  646. expect.objectContaining({
  647. query: expect.objectContaining({
  648. per_page: 5,
  649. }),
  650. })
  651. );
  652. });
  653. it('has sticky table headers', async function () {
  654. const tableWidget: Widget = {
  655. title: 'Table Widget',
  656. interval: '5m',
  657. displayType: DisplayType.TABLE,
  658. widgetType: WidgetType.DISCOVER,
  659. queries: [
  660. {
  661. conditions: '',
  662. fields: ['transaction', 'count()'],
  663. columns: ['transaction'],
  664. aggregates: ['count()'],
  665. name: 'Table',
  666. orderby: '',
  667. },
  668. ],
  669. };
  670. render(
  671. <WidgetCard
  672. api={api}
  673. organization={organizationWithFlag}
  674. widget={tableWidget}
  675. selection={selection}
  676. isEditing={false}
  677. onDelete={() => undefined}
  678. onEdit={() => undefined}
  679. onDuplicate={() => undefined}
  680. renderErrorMessage={() => undefined}
  681. isSorting={false}
  682. currentWidgetDragging={false}
  683. showContextMenu
  684. widgetLimitReached={false}
  685. tableItemLimit={20}
  686. />
  687. );
  688. await tick();
  689. expect(SimpleTableChart).toHaveBeenCalledWith(
  690. expect.objectContaining({stickyHeaders: true}),
  691. expect.anything()
  692. );
  693. });
  694. it('renders stored data disclaimer', async function () {
  695. MockApiClient.addMockResponse({
  696. url: '/organizations/org-slug/events/',
  697. body: {
  698. meta: {fields: {title: 'string'}, isMetricsData: false},
  699. data: [{title: 'title'}],
  700. },
  701. });
  702. render(
  703. <WidgetCard
  704. api={api}
  705. organization={{
  706. ...organizationWithFlag,
  707. features: [...organizationWithFlag.features, 'dashboards-mep'],
  708. }}
  709. widget={{
  710. ...multipleQueryWidget,
  711. displayType: DisplayType.TABLE,
  712. queries: [{...multipleQueryWidget.queries[0]}],
  713. }}
  714. selection={selection}
  715. isEditing={false}
  716. onDelete={() => undefined}
  717. onEdit={() => undefined}
  718. onDuplicate={() => undefined}
  719. renderErrorMessage={() => undefined}
  720. isSorting={false}
  721. currentWidgetDragging={false}
  722. showContextMenu
  723. widgetLimitReached={false}
  724. showStoredAlert
  725. />,
  726. {context: routerContext}
  727. );
  728. await waitFor(() => {
  729. // Badge in the widget header
  730. expect(screen.getByText('Stored')).toBeInTheDocument();
  731. });
  732. await waitFor(() => {
  733. expect(
  734. // Alert below the widget
  735. screen.getByText(/we've automatically adjusted your results/i)
  736. ).toBeInTheDocument();
  737. });
  738. });
  739. });
  740. });