sentryAppRuleModal.spec.tsx 5.2 KB

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