newIssueExperienceButton.spec.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import {LocationFixture} from 'sentry-fixture/locationFixture';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {UserFixture} from 'sentry-fixture/user';
  4. import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import ConfigStore from 'sentry/stores/configStore';
  6. import {trackAnalytics} from 'sentry/utils/analytics';
  7. import {NewIssueExperienceButton} from 'sentry/views/issueDetails/actions/newIssueExperienceButton';
  8. const mockUseNavigate = jest.fn();
  9. jest.mock('sentry/utils/useNavigate', () => ({
  10. useNavigate: () => mockUseNavigate,
  11. }));
  12. jest.mock('sentry/utils/analytics');
  13. const mockFeedbackForm = jest.fn();
  14. jest.mock('sentry/utils/useFeedbackForm', () => ({
  15. useFeedbackForm: () => mockFeedbackForm(),
  16. }));
  17. describe('NewIssueExperienceButton', function () {
  18. const organization = OrganizationFixture({features: ['issue-details-streamline']});
  19. const user = UserFixture();
  20. user.options.prefersIssueDetailsStreamlinedUI = true;
  21. const location = LocationFixture({query: {streamline: '1'}});
  22. beforeEach(() => {
  23. ConfigStore.init();
  24. jest.clearAllMocks();
  25. });
  26. it('does not appear by default', function () {
  27. render(
  28. <div data-test-id="test-id">
  29. <NewIssueExperienceButton />
  30. </div>
  31. );
  32. expect(screen.getByTestId('test-id')).toBeEmptyDOMElement();
  33. });
  34. it('appears when organization has flag', function () {
  35. render(
  36. <div data-test-id="test-id">
  37. <NewIssueExperienceButton />
  38. </div>,
  39. {organization}
  40. );
  41. expect(screen.getByTestId('test-id')).not.toBeEmptyDOMElement();
  42. });
  43. it('does not appear even if user prefers this UI', function () {
  44. act(() => ConfigStore.set('user', user));
  45. render(
  46. <div data-test-id="test-id">
  47. <NewIssueExperienceButton />
  48. </div>
  49. );
  50. expect(screen.getByTestId('test-id')).toBeEmptyDOMElement();
  51. });
  52. it('does not appear when query param is set', function () {
  53. render(
  54. <div data-test-id="test-id">
  55. <NewIssueExperienceButton />
  56. </div>,
  57. {router: {location}}
  58. );
  59. expect(screen.getByTestId('test-id')).toBeEmptyDOMElement();
  60. });
  61. it('triggers changes to the user config and location', async function () {
  62. const mockChangeUserSettings = MockApiClient.addMockResponse({
  63. url: '/users/me/',
  64. method: 'PUT',
  65. });
  66. render(<NewIssueExperienceButton />, {organization});
  67. const button = screen.getByRole('button', {
  68. name: 'Switch to the new issue experience',
  69. });
  70. await userEvent.click(button);
  71. // Text should change immediately
  72. expect(
  73. screen.getByRole('button', {name: 'Switch to the old issue experience'})
  74. ).toBeInTheDocument();
  75. // User option should be saved
  76. await waitFor(() => {
  77. expect(mockChangeUserSettings).toHaveBeenCalledWith(
  78. '/users/me/',
  79. expect.objectContaining({
  80. data: {
  81. options: {
  82. prefersIssueDetailsStreamlinedUI: true,
  83. },
  84. },
  85. })
  86. );
  87. });
  88. // Location should update
  89. expect(mockUseNavigate).toHaveBeenCalledWith(
  90. expect.objectContaining({query: {streamline: '1'}})
  91. );
  92. expect(trackAnalytics).toHaveBeenCalledTimes(1);
  93. // Clicking again toggles it off
  94. await userEvent.click(button);
  95. // Old text should be back
  96. expect(
  97. screen.getByRole('button', {name: 'Switch to the new issue experience'})
  98. ).toBeInTheDocument();
  99. // And save the option as false
  100. await waitFor(() => {
  101. expect(mockChangeUserSettings).toHaveBeenCalledWith(
  102. '/users/me/',
  103. expect.objectContaining({
  104. data: {
  105. options: {
  106. prefersIssueDetailsStreamlinedUI: false,
  107. },
  108. },
  109. })
  110. );
  111. });
  112. // Location should update again
  113. expect(mockUseNavigate).toHaveBeenCalledWith(
  114. expect.objectContaining({query: {streamline: '0'}})
  115. );
  116. expect(trackAnalytics).toHaveBeenCalledTimes(2);
  117. });
  118. it('can switch back to the old UI via dropdown', async function () {
  119. const mockFormCallback = jest.fn();
  120. mockFeedbackForm.mockReturnValue(mockFormCallback);
  121. const mockChangeUserSettings = MockApiClient.addMockResponse({
  122. url: '/users/me/',
  123. method: 'PUT',
  124. });
  125. render(<NewIssueExperienceButton />, {organization});
  126. await userEvent.click(
  127. screen.getByRole('button', {
  128. name: 'Switch to the new issue experience',
  129. })
  130. );
  131. expect(
  132. screen.getByRole('button', {
  133. name: 'Switch issue experience',
  134. })
  135. ).toBeInTheDocument();
  136. const dropdownButton = screen.getByRole('button', {
  137. name: 'Switch issue experience',
  138. });
  139. await userEvent.click(dropdownButton);
  140. await userEvent.click(
  141. await screen.findByRole('menuitemradio', {name: 'Give feedback on new UI'})
  142. );
  143. expect(mockFeedbackForm).toHaveBeenCalled();
  144. await userEvent.click(dropdownButton);
  145. await userEvent.click(
  146. screen.getByRole('menuitemradio', {
  147. name: 'Switch to the old issue experience',
  148. })
  149. );
  150. expect(mockChangeUserSettings).toHaveBeenCalledTimes(2);
  151. });
  152. });