timeRangeSelector.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {fireEvent, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import {TimeRangeSelector} from 'sentry/components/timeRangeSelector';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. const {organization, routerContext} = initializeOrg({
  6. organization: {features: ['global-views', 'open-membership']},
  7. project: undefined,
  8. projects: [
  9. {id: '1', slug: 'project-1', isMember: true},
  10. {id: '2', slug: 'project-2', isMember: true},
  11. {id: '3', slug: 'project-3', isMember: false},
  12. ],
  13. router: {
  14. location: {
  15. pathname: '/organizations/org-slug/issues/',
  16. query: {},
  17. },
  18. params: {},
  19. },
  20. });
  21. describe('TimeRangeSelector', function () {
  22. const onChange = jest.fn();
  23. function getComponent(props = {}) {
  24. return <TimeRangeSelector showAbsolute showRelative onChange={onChange} {...props} />;
  25. }
  26. function renderComponent(props = {}) {
  27. return render(getComponent(props), {context: routerContext});
  28. }
  29. beforeEach(function () {
  30. ConfigStore.loadInitialData(
  31. TestStubs.Config({
  32. user: {options: {timezone: 'America/New_York'}},
  33. })
  34. );
  35. onChange.mockReset();
  36. });
  37. it('renders when given relative period', function () {
  38. renderComponent({relative: '9d'});
  39. expect(screen.getByRole('button', {name: '9D'})).toBeInTheDocument();
  40. });
  41. it('renders when given an invalid relative period', function () {
  42. render(<TimeRangeSelector relative="1y" />, {context: routerContext, organization});
  43. expect(screen.getByRole('button', {name: 'Invalid Period'})).toBeInTheDocument();
  44. });
  45. it('hides relative options', async function () {
  46. renderComponent({showRelative: false, start: '0', end: '0'});
  47. await userEvent.click(screen.getByRole('button', {expanded: false}));
  48. // Ensure none of the relative options are shown
  49. expect(screen.queryByRole('option', {name: 'Last 1 hour'})).not.toBeInTheDocument();
  50. expect(screen.queryByRole('option', {name: 'Last 24 hours'})).not.toBeInTheDocument();
  51. expect(screen.queryByRole('option', {name: 'Last 7 days'})).not.toBeInTheDocument();
  52. expect(screen.queryByRole('option', {name: 'Last 14 days'})).not.toBeInTheDocument();
  53. expect(screen.queryByRole('option', {name: 'Last 30 days'})).not.toBeInTheDocument();
  54. expect(screen.queryByRole('option', {name: 'Last 90 days'})).not.toBeInTheDocument();
  55. // Absolute selector is shown
  56. expect(screen.getByTestId('date-range')).toBeInTheDocument();
  57. });
  58. it('hides absolute selector', async function () {
  59. renderComponent({showAbsolute: false});
  60. await userEvent.click(screen.getByRole('button', {expanded: false}));
  61. // Absolute option & selector are shown
  62. expect(screen.queryByRole('option', {name: 'Absolute date'})).not.toBeInTheDocument();
  63. expect(screen.queryByTestId('date-range')).not.toBeInTheDocument();
  64. });
  65. it('can select an absolute date range', async function () {
  66. renderComponent();
  67. await userEvent.click(screen.getByRole('button', {expanded: false}));
  68. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  69. expect(screen.getByTestId('date-range')).toBeInTheDocument();
  70. const fromDateInput = screen.getByTestId('date-range-primary-from');
  71. const toDateInput = screen.getByTestId('date-range-primary-to');
  72. expect(fromDateInput).toHaveValue('2017-10-02');
  73. expect(toDateInput).toHaveValue('2017-10-16');
  74. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  75. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  76. fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
  77. fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
  78. // Selecting new date range resets time inputs to start/end of day
  79. expect(screen.getByTestId('startTime')).toHaveValue('00:00');
  80. expect(screen.getByTestId('endTime')).toHaveValue('23:59');
  81. // onChange is called after clicking Apply
  82. await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
  83. expect(onChange).toHaveBeenLastCalledWith({
  84. relative: null,
  85. start: new Date('2017-10-03T00:00:00'), // local time
  86. end: new Date('2017-10-04T23:59:59'), // local time
  87. utc: false,
  88. });
  89. });
  90. it('can select an absolute range with utc enabled', async function () {
  91. renderComponent({utc: true});
  92. await userEvent.click(screen.getByRole('button', {expanded: false}));
  93. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  94. expect(await screen.findByTestId('date-range')).toBeInTheDocument();
  95. const fromDateInput = screen.getByTestId('date-range-primary-from');
  96. const toDateInput = screen.getByTestId('date-range-primary-to');
  97. expect(fromDateInput).toHaveValue('2017-10-02');
  98. expect(toDateInput).toHaveValue('2017-10-16');
  99. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  100. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  101. fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
  102. fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
  103. // Selecting new date range resets time inputs to start/end of day
  104. expect(screen.getByTestId('startTime')).toHaveValue('00:00');
  105. expect(screen.getByTestId('endTime')).toHaveValue('23:59');
  106. // onChange is called after clicking Apply
  107. await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
  108. expect(onChange).toHaveBeenLastCalledWith({
  109. relative: null,
  110. start: new Date('2017-10-03T00:00:00Z'), // utc time
  111. end: new Date('2017-10-04T23:59:59Z'), // utc time
  112. utc: true,
  113. });
  114. });
  115. it('keeps time inputs focused while interacting with them', async function () {
  116. renderComponent();
  117. await userEvent.click(screen.getByRole('button', {expanded: false}));
  118. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  119. await userEvent.click(screen.getByTestId('startTime'));
  120. fireEvent.change(screen.getByTestId('startTime'), {target: {value: '05:00'}});
  121. expect(screen.getByTestId('startTime')).toHaveFocus();
  122. await userEvent.click(screen.getByTestId('endTime'));
  123. fireEvent.change(screen.getByTestId('endTime'), {target: {value: '05:00'}});
  124. expect(screen.getByTestId('endTime')).toHaveFocus();
  125. });
  126. it('switches from relative to absolute and then toggling UTC (starting with UTC)', async function () {
  127. renderComponent({relative: '7d', utc: true});
  128. await userEvent.click(screen.getByRole('button', {expanded: false}));
  129. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  130. // Local time is 22:41:20-0500 -- this is what date picker should show
  131. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  132. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  133. await userEvent.click(screen.getByRole('checkbox', {name: 'UTC'}));
  134. // onChange is called after clicking Apply
  135. await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
  136. expect(onChange).toHaveBeenLastCalledWith({
  137. relative: null,
  138. start: new Date('2017-10-10T02:41:20.000Z'),
  139. end: new Date('2017-10-17T02:41:20.000Z'),
  140. utc: false,
  141. });
  142. });
  143. it('switches from relative to absolute and then toggling UTC (starting with non-UTC)', async function () {
  144. renderComponent({relative: '7d', utc: false});
  145. await userEvent.click(screen.getByRole('button', {expanded: false}));
  146. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  147. // Local time is 22:41:20-0500 -- this is what date picker should show
  148. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  149. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  150. await userEvent.click(screen.getByRole('checkbox', {name: 'UTC'}));
  151. await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
  152. expect(onChange).toHaveBeenLastCalledWith({
  153. relative: null,
  154. start: new Date('2017-10-09T22:41:20.000Z'),
  155. end: new Date('2017-10-16T22:41:20.000Z'),
  156. utc: true,
  157. });
  158. });
  159. it('uses the default absolute date', async function () {
  160. renderComponent({
  161. defaultAbsolute: {
  162. start: new Date('2017-10-10T00:00:00.000Z'),
  163. end: new Date('2017-10-17T23:59:59.000Z'),
  164. },
  165. });
  166. await userEvent.click(screen.getByRole('button', {expanded: false}));
  167. await userEvent.click(screen.getByRole('option', {name: 'Absolute date'}));
  168. // Time inputs show local time
  169. expect(screen.getByTestId('startTime')).toHaveValue('20:00');
  170. expect(screen.getByTestId('endTime')).toHaveValue('19:59');
  171. });
  172. it('can select arbitrary relative time ranges', async () => {
  173. renderComponent();
  174. await userEvent.click(screen.getByRole('button', {expanded: false}));
  175. const input = screen.getByRole('textbox');
  176. await userEvent.type(input, '5');
  177. // With just the number "5", all unit options should be present
  178. expect(screen.getByRole('option', {name: 'Last 5 minutes'})).toBeInTheDocument();
  179. expect(screen.getByRole('option', {name: 'Last 5 hours'})).toBeInTheDocument();
  180. expect(screen.getByRole('option', {name: 'Last 5 days'})).toBeInTheDocument();
  181. expect(screen.getByRole('option', {name: 'Last 5 weeks'})).toBeInTheDocument();
  182. await userEvent.type(input, 'd');
  183. // With "5d", only "Last 5 days" should be shown
  184. expect(screen.getByRole('option', {name: 'Last 5 days'})).toBeInTheDocument();
  185. expect(
  186. screen.queryByRole('option', {name: 'Last 5 minutes'})
  187. ).not.toBeInTheDocument();
  188. expect(screen.queryByRole('option', {name: 'Last 5 hours'})).not.toBeInTheDocument();
  189. expect(screen.queryByRole('option', {name: 'Last 5 weeks'})).not.toBeInTheDocument();
  190. await userEvent.type(input, 'ays');
  191. // "5days" Should still show "Last 5 days" option
  192. expect(screen.getByText('Last 5 days')).toBeInTheDocument();
  193. await userEvent.click(screen.getByRole('option', {name: 'Last 5 days'}));
  194. expect(onChange).toHaveBeenLastCalledWith({
  195. relative: '5d',
  196. start: undefined,
  197. end: undefined,
  198. });
  199. });
  200. it('respects maxPickableDays for arbitrary time ranges', async () => {
  201. renderComponent({maxPickableDays: 30});
  202. await userEvent.click(screen.getByRole('button', {expanded: false}));
  203. const input = screen.getByRole('textbox');
  204. await userEvent.type(input, '3');
  205. // With just the number "3", all unit options should be present
  206. expect(screen.getByRole('option', {name: 'Last 3 minutes'})).toBeInTheDocument();
  207. expect(screen.getByRole('option', {name: 'Last 3 hours'})).toBeInTheDocument();
  208. expect(screen.getByRole('option', {name: 'Last 3 days'})).toBeInTheDocument();
  209. expect(screen.getByRole('option', {name: 'Last 3 weeks'})).toBeInTheDocument();
  210. await userEvent.type(input, '1');
  211. // With "31", days and weeks should not be suggested
  212. expect(screen.getByRole('option', {name: 'Last 31 minutes'})).toBeInTheDocument();
  213. expect(screen.getByRole('option', {name: 'Last 31 hours'})).toBeInTheDocument();
  214. expect(screen.queryByRole('option', {name: 'Last 31 days'})).not.toBeInTheDocument();
  215. expect(screen.queryByRole('option', {name: 'Last 31 weeks'})).not.toBeInTheDocument();
  216. await userEvent.type(input, 'd');
  217. // "31d" should return nothing
  218. expect(screen.getByText('No options found')).toBeInTheDocument();
  219. });
  220. it('cannot select arbitrary relative time ranges with disallowArbitraryRelativeRanges', async () => {
  221. renderComponent({disallowArbitraryRelativeRanges: true});
  222. await userEvent.click(screen.getByRole('button', {expanded: false}));
  223. const input = screen.getByRole('textbox');
  224. // Search filter still works normally
  225. await userEvent.type(input, '24');
  226. expect(screen.getByRole('option', {name: 'Last 24 hours'})).toBeInTheDocument();
  227. expect(screen.queryByRole('option', {name: 'Last 1 hour'})).not.toBeInTheDocument();
  228. expect(screen.queryByRole('option', {name: 'Last 7 days'})).not.toBeInTheDocument();
  229. expect(screen.queryByRole('option', {name: 'Last 14 days'})).not.toBeInTheDocument();
  230. expect(screen.queryByRole('option', {name: 'Last 90 days'})).not.toBeInTheDocument();
  231. // But no arbitrary relative range will be suggested
  232. await userEvent.type(input, '5');
  233. expect(screen.getByText('No options found')).toBeInTheDocument();
  234. });
  235. });