eventFeatureFlagList.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {TagsFixture} from 'sentry-fixture/tags';
  3. import {
  4. render,
  5. screen,
  6. userEvent,
  7. waitFor,
  8. waitForDrawerToHide,
  9. } from 'sentry-test/reactTestingLibrary';
  10. import {EventFeatureFlagList} from 'sentry/components/events/featureFlags/eventFeatureFlagList';
  11. import {
  12. EMPTY_STATE_SECTION_PROPS,
  13. MOCK_DATA_SECTION_PROPS,
  14. MOCK_FLAGS,
  15. NO_FLAG_CONTEXT_SECTION_PROPS_CTA,
  16. NO_FLAG_CONTEXT_SECTION_PROPS_NO_CTA,
  17. } from 'sentry/components/events/featureFlags/testUtils';
  18. // Needed to mock useVirtualizer lists.
  19. jest.spyOn(window.Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
  20. x: 0,
  21. y: 0,
  22. width: 0,
  23. height: 30,
  24. left: 0,
  25. top: 0,
  26. right: 0,
  27. bottom: 0,
  28. toJSON: jest.fn(),
  29. }));
  30. describe('EventFeatureFlagList', function () {
  31. beforeEach(function () {
  32. MockApiClient.addMockResponse({
  33. url: '/organizations/org-slug/issues/1/events/',
  34. body: [],
  35. });
  36. MockApiClient.addMockResponse({
  37. url: '/organizations/org-slug/flags/logs/',
  38. body: {data: []},
  39. });
  40. MockApiClient.addMockResponse({
  41. url: '/organizations/org-slug/prompts-activity/',
  42. body: {data: {}},
  43. });
  44. MockApiClient.addMockResponse({
  45. url: '/organizations/org-slug/prompts-activity/',
  46. body: {data: {dismissed_ts: null}},
  47. });
  48. MockApiClient.addMockResponse({
  49. url: `/organizations/org-slug/issues/1/tags/`,
  50. body: TagsFixture(),
  51. });
  52. });
  53. it('renders a list of feature flags with a button to view all', async function () {
  54. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  55. for (const {flag, result} of MOCK_FLAGS) {
  56. if (result) {
  57. expect(screen.getByText(flag)).toBeInTheDocument();
  58. }
  59. }
  60. // When expanded, all should be visible
  61. const viewAllButton = screen.getByRole('button', {name: 'View All'});
  62. await userEvent.click(viewAllButton);
  63. const drawer = screen.getByRole('complementary', {name: 'Feature flags drawer'});
  64. expect(drawer).toBeInTheDocument();
  65. for (const {flag, result} of MOCK_FLAGS) {
  66. if (result) {
  67. expect(screen.getAllByText(flag)[0]).toBeInTheDocument();
  68. }
  69. }
  70. });
  71. it('toggles the drawer when view all is clicked', async function () {
  72. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  73. const viewAllButton = screen.getByRole('button', {name: 'View All'});
  74. await userEvent.click(viewAllButton);
  75. const drawer = screen.getByRole('complementary', {name: 'Feature flags drawer'});
  76. expect(drawer).toBeInTheDocument();
  77. await userEvent.click(viewAllButton);
  78. await waitForDrawerToHide('Feature flags drawer');
  79. expect(drawer).not.toBeInTheDocument();
  80. });
  81. it('opens the drawer and focuses search when the search button is pressed', async function () {
  82. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  83. const control = screen.getByRole('button', {name: 'Open Feature Flag Search'});
  84. expect(control).toBeInTheDocument();
  85. await userEvent.click(control);
  86. expect(
  87. screen.getByRole('complementary', {name: 'Feature flags drawer'})
  88. ).toBeInTheDocument();
  89. const drawerControl = screen.getByRole('textbox', {
  90. name: 'Search Flags',
  91. });
  92. expect(drawerControl).toBeInTheDocument();
  93. expect(drawerControl).toHaveFocus();
  94. });
  95. it('renders a sort dropdown with Evaluation Order as the default', async function () {
  96. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  97. const control = screen.getByRole('button', {name: 'Sort Flags'});
  98. expect(control).toBeInTheDocument();
  99. await userEvent.click(control);
  100. expect(screen.getByRole('option', {name: 'Evaluation Order'})).toBeInTheDocument();
  101. expect(screen.getByRole('option', {name: 'Alphabetical'})).toBeInTheDocument();
  102. });
  103. it('renders a sort dropdown which affects the granular sort dropdown', async function () {
  104. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  105. const control = screen.getByRole('button', {name: 'Sort Flags'});
  106. expect(control).toBeInTheDocument();
  107. await userEvent.click(control);
  108. await userEvent.click(screen.getByRole('option', {name: 'Alphabetical'}));
  109. await userEvent.click(control);
  110. expect(screen.getByRole('option', {name: 'Alphabetical'})).toHaveAttribute(
  111. 'aria-selected',
  112. 'true'
  113. );
  114. expect(screen.getByRole('option', {name: 'A-Z'})).toHaveAttribute(
  115. 'aria-selected',
  116. 'true'
  117. );
  118. });
  119. it('renders a sort dropdown which disables the appropriate options', async function () {
  120. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  121. const control = screen.getByRole('button', {name: 'Sort Flags'});
  122. expect(control).toBeInTheDocument();
  123. await userEvent.click(control);
  124. await userEvent.click(screen.getByRole('option', {name: 'Alphabetical'}));
  125. await userEvent.click(control);
  126. expect(screen.getByRole('option', {name: 'Alphabetical'})).toHaveAttribute(
  127. 'aria-selected',
  128. 'true'
  129. );
  130. expect(screen.getByRole('option', {name: 'Newest First'})).toHaveAttribute(
  131. 'aria-disabled',
  132. 'true'
  133. );
  134. expect(screen.getByRole('option', {name: 'Oldest First'})).toHaveAttribute(
  135. 'aria-disabled',
  136. 'true'
  137. );
  138. await userEvent.click(screen.getByRole('option', {name: 'Evaluation Order'}));
  139. await userEvent.click(control);
  140. expect(screen.getByRole('option', {name: 'Evaluation Order'})).toHaveAttribute(
  141. 'aria-selected',
  142. 'true'
  143. );
  144. expect(screen.getByRole('option', {name: 'Z-A'})).toHaveAttribute(
  145. 'aria-disabled',
  146. 'true'
  147. );
  148. expect(screen.getByRole('option', {name: 'A-Z'})).toHaveAttribute(
  149. 'aria-disabled',
  150. 'true'
  151. );
  152. });
  153. it('allows sort dropdown to affect displayed flags', async function () {
  154. render(<EventFeatureFlagList {...MOCK_DATA_SECTION_PROPS} />);
  155. const [webVitalsFlag, enableReplay] = MOCK_FLAGS.filter(f => f.result === true);
  156. // the flags are reversed by default
  157. // expect enableReplay to be preceding webVitalsFlag
  158. expect(
  159. screen
  160. .getByText(webVitalsFlag.flag)
  161. .compareDocumentPosition(screen.getByText(enableReplay.flag))
  162. ).toBe(document.DOCUMENT_POSITION_PRECEDING);
  163. const sortControl = screen.getByRole('button', {
  164. name: 'Sort Flags',
  165. });
  166. await userEvent.click(sortControl);
  167. await userEvent.click(screen.getByRole('option', {name: 'Oldest First'}));
  168. // expect enableReplay to be following webVitalsFlag
  169. expect(
  170. screen
  171. .getByText(webVitalsFlag.flag)
  172. .compareDocumentPosition(screen.getByText(enableReplay.flag))
  173. ).toBe(document.DOCUMENT_POSITION_FOLLOWING);
  174. await userEvent.click(sortControl);
  175. await userEvent.click(screen.getByRole('option', {name: 'Alphabetical'}));
  176. // expect enableReplay to be preceding webVitalsFlag, A-Z sort by default
  177. expect(
  178. screen
  179. .getByText(webVitalsFlag.flag)
  180. .compareDocumentPosition(screen.getByText(enableReplay.flag))
  181. ).toBe(document.DOCUMENT_POSITION_PRECEDING);
  182. await userEvent.click(sortControl);
  183. await userEvent.click(screen.getByRole('option', {name: 'Z-A'}));
  184. // expect enableReplay to be following webVitalsFlag
  185. expect(
  186. screen
  187. .getByText(webVitalsFlag.flag)
  188. .compareDocumentPosition(screen.getByText(enableReplay.flag))
  189. ).toBe(document.DOCUMENT_POSITION_FOLLOWING);
  190. });
  191. it('renders empty state', function () {
  192. render(<EventFeatureFlagList {...EMPTY_STATE_SECTION_PROPS} />);
  193. const control = screen.queryByRole('button', {name: 'Sort Flags'});
  194. expect(control).not.toBeInTheDocument();
  195. const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
  196. expect(search).not.toBeInTheDocument();
  197. expect(screen.getByRole('button', {name: 'Set Up Integration'})).toBeInTheDocument();
  198. expect(
  199. screen.getByText('No feature flags were found for this event')
  200. ).toBeInTheDocument();
  201. });
  202. it('renders cta if event.contexts.flags is not set and should show cta', async function () {
  203. const org = OrganizationFixture({features: ['feature-flag-cta']});
  204. render(<EventFeatureFlagList {...NO_FLAG_CONTEXT_SECTION_PROPS_CTA} />, {
  205. organization: org,
  206. });
  207. const control = screen.queryByRole('button', {name: 'Sort Flags'});
  208. expect(control).not.toBeInTheDocument();
  209. const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
  210. expect(search).not.toBeInTheDocument();
  211. expect(
  212. screen.queryByRole('button', {name: 'Set Up Integration'})
  213. ).not.toBeInTheDocument();
  214. // wait for the CTA to be rendered
  215. expect(await screen.findByText('Set Up Feature Flags')).toBeInTheDocument();
  216. expect(screen.getByText('Feature Flags')).toBeInTheDocument();
  217. });
  218. it('renders nothing if event.contexts.flags is not set and should not show cta - wrong platform', async function () {
  219. const org = OrganizationFixture({features: ['feature-flag-cta']});
  220. render(<EventFeatureFlagList {...NO_FLAG_CONTEXT_SECTION_PROPS_NO_CTA} />, {
  221. organization: org,
  222. });
  223. const control = screen.queryByRole('button', {name: 'Sort Flags'});
  224. expect(control).not.toBeInTheDocument();
  225. const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
  226. expect(search).not.toBeInTheDocument();
  227. expect(
  228. screen.queryByRole('button', {name: 'Set Up Integration'})
  229. ).not.toBeInTheDocument();
  230. // CTA should not appear
  231. await waitFor(() => {
  232. expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
  233. });
  234. expect(screen.queryByText('Feature Flags')).not.toBeInTheDocument();
  235. });
  236. it('renders nothing if event.contexts.flags is not set and should not show cta - no feature flag', async function () {
  237. const org = OrganizationFixture({features: ['fake-feature-flag']});
  238. render(<EventFeatureFlagList {...NO_FLAG_CONTEXT_SECTION_PROPS_CTA} />, {
  239. organization: org,
  240. });
  241. const control = screen.queryByRole('button', {name: 'Sort Flags'});
  242. expect(control).not.toBeInTheDocument();
  243. const search = screen.queryByRole('button', {name: 'Open Feature Flag Search'});
  244. expect(search).not.toBeInTheDocument();
  245. expect(
  246. screen.queryByRole('button', {name: 'Set Up Integration'})
  247. ).not.toBeInTheDocument();
  248. // CTA should not appear
  249. await waitFor(() => {
  250. expect(screen.queryByText('Set Up Feature Flags')).not.toBeInTheDocument();
  251. });
  252. expect(screen.queryByText('Feature Flags')).not.toBeInTheDocument();
  253. });
  254. });