arithmeticInput.spec.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {Column, generateFieldAsString} from 'sentry/utils/discover/fields';
  3. import ArithmeticInput from 'sentry/views/eventsV2/table/arithmeticInput';
  4. describe('ArithmeticInput', function () {
  5. let wrapper;
  6. let query: string;
  7. let handleQueryChange: (value: string) => void;
  8. let numericColumns: Column[];
  9. let columns: Column[];
  10. const operators = ['+', '-', '*', '/', '(', ')'];
  11. beforeEach(function () {
  12. query = '';
  13. handleQueryChange = q => {
  14. query = q;
  15. };
  16. numericColumns = [
  17. {kind: 'field', field: 'transaction.duration'},
  18. {kind: 'field', field: 'measurements.lcp'},
  19. {kind: 'field', field: 'spans.http'},
  20. {kind: 'function', function: ['p50', '', undefined, undefined]},
  21. {
  22. kind: 'function',
  23. function: ['percentile', 'transaction.duration', '0.25', undefined],
  24. },
  25. {kind: 'function', function: ['count', '', undefined, undefined]},
  26. ];
  27. columns = [
  28. ...numericColumns,
  29. // these columns will not be rendered in the dropdown
  30. {kind: 'function', function: ['any', 'transaction.duration', undefined, undefined]},
  31. {kind: 'field', field: 'transaction'},
  32. {kind: 'function', function: ['failure_rate', '', undefined, undefined]},
  33. {kind: 'equation', field: 'transaction.duration+measurements.lcp'},
  34. ];
  35. wrapper = mountWithTheme(
  36. <ArithmeticInput
  37. name="refinement"
  38. key="parameter:text"
  39. type="text"
  40. required
  41. value={query}
  42. onUpdate={handleQueryChange}
  43. options={columns}
  44. />
  45. );
  46. });
  47. afterEach(function () {
  48. wrapper?.unmount();
  49. });
  50. it('can toggle autocomplete dropdown on focus and blur', function () {
  51. expect(wrapper.find('TermDropdown').props().isOpen).toBeFalsy();
  52. // focus the input
  53. wrapper.find('input').simulate('focus');
  54. expect(wrapper.find('TermDropdown').props().isOpen).toBeTruthy();
  55. // blur the input
  56. wrapper.find('input').simulate('blur');
  57. expect(wrapper.find('TermDropdown').props().isOpen).toBeFalsy();
  58. });
  59. it('renders only numeric options in autocomplete', function () {
  60. wrapper.find('input').simulate('focus');
  61. const options = wrapper.find('DropdownListItem');
  62. expect(options).toHaveLength(numericColumns.length + operators.length);
  63. options.forEach((option, i) => {
  64. if (i < numericColumns.length) {
  65. expect(option.text()).toEqual(generateFieldAsString(numericColumns[i]));
  66. } else {
  67. expect(option.text()).toEqual(operators[i - numericColumns.length]);
  68. }
  69. });
  70. });
  71. it('can use keyboard to select an option', function () {
  72. const input = wrapper.find('input');
  73. input.simulate('focus');
  74. expect(wrapper.find('DropdownListItem .active').exists()).toBeFalsy();
  75. for (const column of numericColumns) {
  76. input.simulate('keydown', {key: 'ArrowDown'});
  77. expect(wrapper.find('DropdownListItem .active').text()).toEqual(
  78. generateFieldAsString(column)
  79. );
  80. }
  81. for (const operator of operators) {
  82. input.simulate('keydown', {key: 'ArrowDown'});
  83. expect(wrapper.find('DropdownListItem .active').text()).toEqual(operator);
  84. }
  85. // wrap around to the first option again
  86. input.simulate('keydown', {key: 'ArrowDown'});
  87. for (const operator of [...operators].reverse()) {
  88. input.simulate('keydown', {key: 'ArrowUp'});
  89. expect(wrapper.find('DropdownListItem .active').text()).toEqual(operator);
  90. }
  91. for (const column of [...numericColumns].reverse()) {
  92. input.simulate('keydown', {key: 'ArrowUp'});
  93. expect(wrapper.find('DropdownListItem .active').text()).toEqual(
  94. generateFieldAsString(column)
  95. );
  96. }
  97. // the update is buffered until blur happens
  98. input.simulate('keydown', {key: 'Enter'});
  99. expect(query).toEqual('');
  100. input.simulate('blur');
  101. expect(query).toEqual(`${generateFieldAsString(numericColumns[0])} `);
  102. });
  103. it('can use mouse to select an option', function () {
  104. const input = wrapper.find('input');
  105. input.simulate('focus');
  106. // the update is buffered until blur happens
  107. wrapper.find('DropdownListItem').first().simulate('click');
  108. input.simulate('blur');
  109. expect(query).toEqual(`${generateFieldAsString(numericColumns[0])} `);
  110. });
  111. it('autocompletes the current term when it is in the front', function () {
  112. const input = wrapper.find('input');
  113. input.simulate('focus');
  114. const value = 'lcp + transaction.duration';
  115. input.simulate('change', {target: {value}});
  116. const inputElem = input.getDOMNode();
  117. inputElem.selectionStart = 2;
  118. inputElem.selectionEnd = 2;
  119. input.simulate('change');
  120. const option = wrapper.find('DropdownListItem');
  121. expect(option).toHaveLength(1);
  122. expect(option.text()).toEqual(
  123. generateFieldAsString({
  124. kind: 'field',
  125. field: 'measurements.lcp',
  126. })
  127. );
  128. option.simulate('click');
  129. input.simulate('blur');
  130. expect(query).toEqual(`measurements.lcp + transaction.duration`);
  131. });
  132. it('autocompletes the current term when it is in the end', function () {
  133. const input = wrapper.find('input');
  134. input.simulate('focus');
  135. const value = 'transaction.duration + lcp';
  136. input.simulate('change', {target: {value}});
  137. const inputElem = input.getDOMNode();
  138. inputElem.selectionStart = value.length - 1;
  139. inputElem.selectionEnd = value.length - 1;
  140. input.simulate('change');
  141. const option = wrapper.find('DropdownListItem');
  142. expect(option).toHaveLength(1);
  143. const column = numericColumns.find(
  144. c => c.kind === 'field' && c.field.includes('lcp')
  145. );
  146. expect(option.text()).toEqual(generateFieldAsString(column!));
  147. option.simulate('click');
  148. input.simulate('blur');
  149. expect(query).toEqual(`transaction.duration + measurements.lcp `);
  150. });
  151. it('handles autocomplete on invalid term', function () {
  152. const input = wrapper.find('input');
  153. input.simulate('focus');
  154. const value = 'foo + bar';
  155. input.simulate('change', {target: {value}});
  156. input.simulate('keydown', {key: 'ArrowDown'});
  157. const option = wrapper.find('DropdownListItem');
  158. expect(option).toHaveLength(0);
  159. });
  160. it('can hide Fields options', function () {
  161. wrapper = mountWithTheme(
  162. <ArithmeticInput
  163. name="refinement"
  164. type="text"
  165. required
  166. value=""
  167. onUpdate={() => {}}
  168. options={[]}
  169. hideFieldOptions
  170. />
  171. );
  172. const optionGroupHeaders = wrapper.find('header');
  173. expect(optionGroupHeaders).toHaveLength(1);
  174. expect(optionGroupHeaders.text()).toBe('Operators');
  175. });
  176. });