newIssueExperienceButton.spec.tsx 5.1 KB

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