feedbackModal.spec.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import {Fragment} from 'react';
  2. import {BrowserClient} from '@sentry/react';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  5. import * as indicators from 'sentry/actionCreators/indicator';
  6. import {openModal} from 'sentry/actionCreators/modal';
  7. import {FeedbackModal} from 'sentry/components/featureFeedback/feedbackModal';
  8. import GlobalModal from 'sentry/components/globalModal';
  9. import {RouteContext} from 'sentry/views/routeContext';
  10. import {TextField} from '../forms';
  11. function ComponentProviders({children}: {children: React.ReactNode}) {
  12. const {router} = initializeOrg();
  13. return (
  14. <RouteContext.Provider
  15. value={{
  16. router,
  17. location: router.location,
  18. params: {},
  19. routes: [],
  20. }}
  21. >
  22. {children}
  23. </RouteContext.Provider>
  24. );
  25. }
  26. describe('FeatureFeedback', function () {
  27. describe('default', function () {
  28. it('submits modal on click', function () {
  29. jest.spyOn(indicators, 'addSuccessMessage');
  30. render(<GlobalModal />);
  31. openModal(modalProps => (
  32. <ComponentProviders>
  33. <FeedbackModal {...modalProps} featureName="test" />
  34. </ComponentProviders>
  35. ));
  36. // Form fields
  37. expect(screen.getByText('Select type of feedback')).toBeInTheDocument();
  38. expect(screen.getByPlaceholderText('What did you expect?')).toBeInTheDocument();
  39. // Form actions
  40. expect(screen.getByRole('button', {name: 'Cancel'})).toBeInTheDocument();
  41. expect(screen.getByRole('button', {name: 'Submit Feedback'})).toBeDisabled();
  42. // User enters additional feedback message
  43. userEvent.paste(
  44. screen.getByPlaceholderText('What did you expect?'),
  45. 'this is a feedback message'
  46. );
  47. userEvent.keyboard('{enter}');
  48. // Submit button is still disabled
  49. expect(screen.getByRole('button', {name: 'Submit Feedback'})).toBeDisabled();
  50. userEvent.click(screen.getByText('Select type of feedback'));
  51. // Available feedback types
  52. expect(screen.getByText("I don't like this feature")).toBeInTheDocument();
  53. expect(screen.getByText('Other reason')).toBeInTheDocument();
  54. expect(screen.getByText('I like this feature')).toBeInTheDocument();
  55. // Select feedback type
  56. userEvent.click(screen.getByText('I like this feature'));
  57. // Submit button is now enabled because the required field was selected
  58. expect(screen.getByRole('button', {name: 'Submit Feedback'})).toBeEnabled();
  59. userEvent.click(screen.getByRole('button', {name: 'Submit Feedback'}));
  60. expect(indicators.addSuccessMessage).toHaveBeenCalledWith(
  61. 'Thanks for taking the time to provide us feedback!'
  62. );
  63. });
  64. it('renders provided feedbackTypes', function () {
  65. render(<GlobalModal />);
  66. openModal(modalProps => (
  67. <ComponentProviders>
  68. <FeedbackModal
  69. {...modalProps}
  70. featureName="test"
  71. feedbackTypes={['Custom feedback type A', 'Custom feedback type B']}
  72. />
  73. </ComponentProviders>
  74. ));
  75. userEvent.click(screen.getByText('Select type of feedback'));
  76. // Available feedback types
  77. expect(screen.getByText('Custom feedback type A')).toBeInTheDocument();
  78. expect(screen.getByText('Custom feedback type B')).toBeInTheDocument();
  79. // Close modal
  80. userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  81. });
  82. it('renders an arbitrary secondary action', function () {
  83. render(<GlobalModal />);
  84. openModal(modalProps => (
  85. <ComponentProviders>
  86. <FeedbackModal
  87. {...modalProps}
  88. featureName="test"
  89. secondaryAction={<a href="#">Test Secondary Action Link</a>}
  90. />
  91. </ComponentProviders>
  92. ));
  93. userEvent.click(screen.getByText('Select type of feedback'));
  94. // Available feedback types
  95. expect(screen.getByText('Test Secondary Action Link')).toBeInTheDocument();
  96. // Close modal
  97. userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  98. });
  99. });
  100. describe('custom', function () {
  101. it('renders custom feedback form', function () {
  102. jest.spyOn(indicators, 'addSuccessMessage');
  103. // Mock implementation of the Sentry Browser SDK
  104. BrowserClient.prototype.captureEvent = jest.fn();
  105. render(<GlobalModal />);
  106. openModal(modalProps => (
  107. <ComponentProviders>
  108. <FeedbackModal
  109. {...modalProps}
  110. featureName="test"
  111. initialData={{step: 0, name: null, surname: null}}
  112. >
  113. {({Header, Body, Footer, state, onFieldChange}) => {
  114. if (state.step === 0) {
  115. return (
  116. <Fragment>
  117. <Header>First Step</Header>
  118. <Body>
  119. <TextField
  120. label="Name"
  121. value={state.name}
  122. name="name"
  123. onChange={value => onFieldChange('name', value)}
  124. />
  125. </Body>
  126. <Footer onNext={() => onFieldChange('step', 1)} />
  127. </Fragment>
  128. );
  129. }
  130. return (
  131. <Fragment>
  132. <Header>Last Step</Header>
  133. <Body>
  134. <TextField
  135. label="Surname"
  136. value={state.surname}
  137. name="surname"
  138. onChange={value => onFieldChange('surname', value)}
  139. />
  140. </Body>
  141. <Footer
  142. onBack={() => onFieldChange('step', 0)}
  143. primaryDisabledReason={
  144. !state.surname ? 'Please answer at least one question' : undefined
  145. }
  146. submitEventData={{message: 'Feedback: test'}}
  147. />
  148. </Fragment>
  149. );
  150. }}
  151. </FeedbackModal>
  152. </ComponentProviders>
  153. ));
  154. // Does not render the default form
  155. expect(screen.queryByText('Select type of feedback')).not.toBeInTheDocument();
  156. // Custom form
  157. expect(screen.getByRole('heading', {name: 'First Step'})).toBeInTheDocument();
  158. // Change form field
  159. expect(screen.getByRole('textbox', {name: 'Name'})).toHaveValue('');
  160. userEvent.type(screen.getByRole('textbox', {name: 'Name'}), 'new value');
  161. expect(screen.getByRole('textbox', {name: 'Name'})).toHaveValue('new value');
  162. // Go to next step
  163. userEvent.click(screen.getByRole('button', {name: 'Next'}));
  164. // Next step is rendered
  165. expect(screen.getByRole('heading', {name: 'Last Step'})).toBeInTheDocument();
  166. expect(screen.getByRole('button', {name: 'Back'})).toBeInTheDocument();
  167. // Go to previous step
  168. userEvent.click(screen.getByRole('button', {name: 'Back'}));
  169. // Previous step is rendered
  170. expect(screen.getByRole('heading', {name: 'First Step'})).toBeInTheDocument();
  171. // Go to next step
  172. userEvent.click(screen.getByRole('button', {name: 'Next'}));
  173. // Next step is rendered
  174. expect(screen.getByRole('button', {name: 'Submit Feedback'})).toBeDisabled();
  175. // Change form field
  176. expect(screen.getByRole('textbox', {name: 'Surname'})).toHaveValue('');
  177. userEvent.type(screen.getByRole('textbox', {name: 'Surname'}), 'new value');
  178. expect(screen.getByRole('textbox', {name: 'Surname'})).toHaveValue('new value');
  179. expect(screen.getByRole('button', {name: 'Submit Feedback'})).toBeEnabled();
  180. userEvent.click(screen.getByRole('button', {name: 'Submit Feedback'}));
  181. expect(indicators.addSuccessMessage).toHaveBeenCalledWith(
  182. 'Thanks for taking the time to provide us feedback!'
  183. );
  184. });
  185. });
  186. });