sentryAppRuleModal.spec.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import styled from '@emotion/styled';
  2. import {SentryAppFixture} from 'sentry-fixture/sentryApp';
  3. import {SentryAppInstallationFixture} from 'sentry-fixture/sentryAppInstallation';
  4. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import {makeCloseButton} from 'sentry/components/globalModal/components';
  6. import SentryAppRuleModal from 'sentry/views/alerts/rules/issue/sentryAppRuleModal';
  7. import type {
  8. FieldFromSchema,
  9. SchemaFormConfig,
  10. } from 'sentry/views/settings/organizationIntegrations/sentryAppExternalForm';
  11. describe('SentryAppRuleModal', function () {
  12. const modalElements = {
  13. Header: p => p.children,
  14. Body: p => p.children,
  15. Footer: p => p.children,
  16. };
  17. let sentryApp;
  18. let sentryAppInstallation;
  19. beforeEach(function () {
  20. sentryApp = SentryAppFixture();
  21. sentryAppInstallation = SentryAppInstallationFixture();
  22. });
  23. const _submit = async () => {
  24. await userEvent.click(screen.getByText('Save Changes'));
  25. return screen.queryAllByText('Field is required');
  26. };
  27. const submitSuccess = async () => {
  28. const errors = await _submit();
  29. expect(errors).toHaveLength(0);
  30. };
  31. const submitErrors = async errorCount => {
  32. const errors = await _submit();
  33. expect(errors).toHaveLength(errorCount);
  34. };
  35. const defaultConfig: SchemaFormConfig = {
  36. uri: '/integration/test/',
  37. description: '',
  38. required_fields: [
  39. {
  40. type: 'text',
  41. label: 'Alert Title',
  42. name: 'title',
  43. },
  44. {
  45. type: 'textarea',
  46. label: 'Alert Description',
  47. name: 'description',
  48. },
  49. {
  50. type: 'select',
  51. label: 'Team Channel',
  52. name: 'channel',
  53. choices: [
  54. ['valor', 'valor'],
  55. ['mystic', 'mystic'],
  56. ['instinct', 'instinct'],
  57. ],
  58. },
  59. ],
  60. optional_fields: [
  61. {
  62. type: 'text',
  63. label: 'Extra Details',
  64. name: 'extra',
  65. },
  66. {
  67. type: 'select',
  68. label: 'Assignee',
  69. name: 'assignee',
  70. uri: '/link/assignee/',
  71. },
  72. {
  73. type: 'select',
  74. label: 'Workspace',
  75. name: 'workspace',
  76. uri: '/link/workspace/',
  77. },
  78. ],
  79. };
  80. const resetValues = {
  81. settings: [
  82. {
  83. name: 'extra',
  84. value: 'saved details from last edit',
  85. },
  86. {
  87. name: 'assignee',
  88. value: 'edna-mode',
  89. label: 'Edna Mode',
  90. },
  91. ],
  92. };
  93. const createWrapper = (props = {}) => {
  94. const styledWrapper = styled(c => c.children);
  95. return render(
  96. <SentryAppRuleModal
  97. {...modalElements}
  98. sentryAppInstallationUuid={sentryAppInstallation.uuid}
  99. appName={sentryApp.name}
  100. config={defaultConfig}
  101. onSubmitSuccess={() => {}}
  102. resetValues={resetValues}
  103. closeModal={jest.fn()}
  104. CloseButton={makeCloseButton(() => {})}
  105. Body={styledWrapper()}
  106. Footer={styledWrapper()}
  107. {...props}
  108. />
  109. );
  110. };
  111. describe('Create UI Alert Rule', function () {
  112. it('should render the Alert Rule modal with the config fields', function () {
  113. createWrapper();
  114. const {required_fields, optional_fields} = defaultConfig;
  115. const allFields = [...required_fields!, ...optional_fields!];
  116. allFields.forEach((field: FieldFromSchema) => {
  117. if (typeof field.label === 'string') {
  118. expect(screen.getByText(field.label)).toBeInTheDocument();
  119. }
  120. });
  121. });
  122. it('should raise validation errors when "Save Changes" is clicked with invalid data', async function () {
  123. createWrapper();
  124. await submitErrors(3);
  125. });
  126. it('should submit when "Save Changes" is clicked with valid data', async function () {
  127. createWrapper();
  128. const titleInput = screen.getByTestId('title');
  129. await userEvent.type(titleInput, 'some title');
  130. const descriptionInput = screen.getByTestId('description');
  131. await userEvent.type(descriptionInput, 'some description');
  132. const channelInput = screen.getAllByText('Type to search')[0];
  133. await userEvent.type(channelInput, '{keyDown}');
  134. await userEvent.click(screen.getByText('valor'));
  135. // Ensure text fields are persisted on edit
  136. const savedExtraDetailsInput = screen.getByDisplayValue(
  137. resetValues.settings[0].value
  138. );
  139. expect(savedExtraDetailsInput).toBeInTheDocument();
  140. // Ensure select fields are persisted with labels on edit
  141. const savedAssigneeInput = screen.getByText(resetValues.settings[1].label!);
  142. expect(savedAssigneeInput).toBeInTheDocument();
  143. // Ensure async select fields filter correctly
  144. const workspaceChoices = [
  145. ['WS0', 'Primary Workspace'],
  146. ['WS1', 'Secondary Workspace'],
  147. ];
  148. const workspaceResponse = MockApiClient.addMockResponse({
  149. url: `/sentry-app-installations/${sentryAppInstallation.uuid}/external-requests/`,
  150. body: {choices: workspaceChoices},
  151. });
  152. const workspaceInput = screen.getByText('Type to search');
  153. // Search by value
  154. await userEvent.type(workspaceInput, workspaceChoices[1][0]);
  155. await waitFor(() => expect(workspaceResponse).toHaveBeenCalled());
  156. // Select by label
  157. await userEvent.click(screen.getByText(workspaceChoices[1][1]));
  158. await submitSuccess();
  159. });
  160. });
  161. });