sentryAppRuleModal.spec.tsx 5.1 KB

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