feedbackModal.spec.tsx 7.7 KB

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