index.spec.jsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import {fireEvent, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  2. import TimeRangeSelector from 'sentry/components/organizations/timeRangeSelector';
  3. import ConfigStore from 'sentry/stores/configStore';
  4. describe('TimeRangeSelector', function () {
  5. const onChange = jest.fn();
  6. const routerContext = TestStubs.routerContext();
  7. const organization = TestStubs.Organization();
  8. function getComponent(props = {}) {
  9. return (
  10. <TimeRangeSelector
  11. showAbsolute
  12. showRelative
  13. onChange={onChange}
  14. organization={organization}
  15. {...props}
  16. />
  17. );
  18. }
  19. function renderComponent(props = {}) {
  20. return render(getComponent(props), {context: routerContext});
  21. }
  22. beforeEach(function () {
  23. ConfigStore.loadInitialData({
  24. user: {options: {timezone: 'America/New_York'}},
  25. });
  26. onChange.mockReset();
  27. });
  28. it('renders when given relative period not in dropdown', function () {
  29. render(
  30. <TimeRangeSelector
  31. organization={organization}
  32. showAbsolute={false}
  33. showRelative={false}
  34. relative="9d"
  35. />,
  36. {context: routerContext}
  37. );
  38. expect(screen.getByText('Last 9 days')).toBeInTheDocument();
  39. });
  40. it('renders when given an invalid relative period', function () {
  41. render(
  42. <TimeRangeSelector
  43. organization={organization}
  44. showAbsolute={false}
  45. showRelative={false}
  46. relative="1y"
  47. />,
  48. {context: routerContext}
  49. );
  50. expect(screen.getByText('Invalid period')).toBeInTheDocument();
  51. });
  52. it('hides relative and absolute selectors', function () {
  53. render(
  54. <TimeRangeSelector
  55. organization={organization}
  56. showAbsolute={false}
  57. showRelative={false}
  58. />,
  59. {context: routerContext}
  60. );
  61. userEvent.click(screen.getByRole('button'));
  62. // Ensure none of the relative options are shown
  63. expect(screen.queryByTestId('1h')).not.toBeInTheDocument();
  64. expect(screen.queryByTestId('24h')).not.toBeInTheDocument();
  65. expect(screen.queryByTestId('7d')).not.toBeInTheDocument();
  66. expect(screen.queryByTestId('14d')).not.toBeInTheDocument();
  67. expect(screen.queryByTestId('30d')).not.toBeInTheDocument();
  68. expect(screen.queryByTestId('90d')).not.toBeInTheDocument();
  69. // Ensure absolute option not shown
  70. expect(screen.queryByTestId('absolute')).not.toBeInTheDocument();
  71. });
  72. it('does not open selector menu when disabled', function () {
  73. renderComponent({disabled: true});
  74. userEvent.click(screen.getByRole('button'));
  75. // Dropdown not open
  76. expect(screen.queryByText(/last hour/i)).not.toBeInTheDocument();
  77. });
  78. it('can select an absolute date range', async function () {
  79. renderComponent();
  80. userEvent.click(screen.getByRole('button'));
  81. expect(screen.queryByTestId('date-range')).not.toBeInTheDocument();
  82. userEvent.click(await screen.findByTestId('absolute'));
  83. const newProps = {
  84. relative: null,
  85. start: new Date('2017-10-03T02:41:20.000Z'),
  86. end: new Date('2017-10-17T02:41:20.000Z'),
  87. };
  88. expect(onChange).toHaveBeenLastCalledWith(newProps);
  89. expect(await screen.findByTestId('date-range')).toBeInTheDocument();
  90. const fromDateInput = screen.getByTestId('date-range-primary-from');
  91. const toDateInput = screen.getByTestId('date-range-primary-to');
  92. expect(fromDateInput).toHaveValue('2017-10-02');
  93. expect(toDateInput).toHaveValue('2017-10-16');
  94. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  95. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  96. fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
  97. fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
  98. // Selecting new date range resets time inputs to start/end of day
  99. expect(onChange).toHaveBeenLastCalledWith({
  100. relative: null,
  101. start: new Date('2017-10-03T00:00:00'), // local time
  102. end: new Date('2017-10-04T23:59:59'), // local time
  103. });
  104. expect(screen.getByTestId('startTime')).toHaveValue('00:00');
  105. expect(screen.getByTestId('endTime')).toHaveValue('23:59');
  106. });
  107. it('can select an absolute range with utc enabled', async function () {
  108. renderComponent({utc: true});
  109. userEvent.click(screen.getByRole('button'));
  110. expect(screen.queryByTestId('date-range')).not.toBeInTheDocument();
  111. userEvent.click(await screen.findByTestId('absolute'));
  112. const newProps = {
  113. relative: null,
  114. start: new Date('2017-10-02T22:41:20.000Z'),
  115. end: new Date('2017-10-16T22:41:20.000Z'),
  116. utc: true,
  117. };
  118. expect(onChange).toHaveBeenLastCalledWith(newProps);
  119. expect(await screen.findByTestId('date-range')).toBeInTheDocument();
  120. const fromDateInput = screen.getByTestId('date-range-primary-from');
  121. const toDateInput = screen.getByTestId('date-range-primary-to');
  122. expect(fromDateInput).toHaveValue('2017-10-02');
  123. expect(toDateInput).toHaveValue('2017-10-16');
  124. expect(screen.getByTestId('startTime')).toHaveValue('22:41');
  125. expect(screen.getByTestId('endTime')).toHaveValue('22:41');
  126. fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
  127. fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
  128. // Selecting new date range resets time inputs to start/end of day
  129. expect(onChange).toHaveBeenLastCalledWith({
  130. relative: null,
  131. start: new Date('2017-10-03T00:00:00Z'), // utc time
  132. end: new Date('2017-10-04T23:59:59Z'), // utc time
  133. utc: true,
  134. });
  135. expect(screen.getByTestId('startTime')).toHaveValue('00:00');
  136. expect(screen.getByTestId('endTime')).toHaveValue('23:59');
  137. });
  138. it('keeps time inputs focused while interacting with them', async function () {
  139. renderComponent();
  140. userEvent.click(screen.getByRole('button'));
  141. userEvent.click(await screen.findByTestId('absolute'));
  142. await screen.findByTestId('date-range');
  143. userEvent.click(screen.getByTestId('startTime'));
  144. fireEvent.change(screen.getByTestId('startTime'), {target: {value: '05:00'}});
  145. expect(screen.getByTestId('startTime')).toHaveFocus();
  146. userEvent.click(screen.getByTestId('endTime'));
  147. fireEvent.change(screen.getByTestId('endTime'), {target: {value: '05:00'}});
  148. expect(screen.getByTestId('endTime')).toHaveFocus();
  149. });
  150. it('switches from relative to absolute while maintaining equivalent date range', async function () {
  151. const {rerender} = renderComponent({
  152. relative: '7d',
  153. utc: false,
  154. });
  155. userEvent.click(screen.getByRole('button'));
  156. userEvent.click(await screen.findByTestId('absolute'));
  157. expect(onChange).toHaveBeenCalledWith({
  158. relative: null,
  159. start: new Date('2017-10-10T02:41:20.000Z'),
  160. end: new Date('2017-10-17T02:41:20.000Z'),
  161. utc: false,
  162. });
  163. userEvent.click(screen.getByTestId('14d'));
  164. expect(onChange).toHaveBeenLastCalledWith({
  165. relative: '14d',
  166. start: undefined,
  167. end: undefined,
  168. });
  169. rerender(
  170. getComponent({
  171. relative: '14d',
  172. utc: false,
  173. })
  174. );
  175. userEvent.click(screen.getByRole('button'));
  176. userEvent.click(await screen.findByTestId('absolute'));
  177. expect(onChange).toHaveBeenLastCalledWith({
  178. relative: null,
  179. start: new Date('2017-10-03T02:41:20.000Z'),
  180. end: new Date('2017-10-17T02:41:20.000Z'),
  181. utc: false,
  182. });
  183. });
  184. it('switches from relative to absolute while maintaining equivalent date range (in utc)', async function () {
  185. const {rerender} = renderComponent({
  186. relative: '7d',
  187. utc: true,
  188. });
  189. userEvent.click(screen.getByRole('button'));
  190. userEvent.click(await screen.findByTestId('absolute'));
  191. expect(onChange).toHaveBeenCalledWith({
  192. relative: null,
  193. start: new Date('2017-10-09T22:41:20.000Z'),
  194. end: new Date('2017-10-16T22:41:20.000Z'),
  195. utc: true,
  196. });
  197. userEvent.click(screen.getByTestId('14d'));
  198. expect(onChange).toHaveBeenLastCalledWith({
  199. relative: '14d',
  200. start: undefined,
  201. end: undefined,
  202. });
  203. rerender(
  204. getComponent({
  205. relative: '14d',
  206. utc: true,
  207. })
  208. );
  209. userEvent.click(screen.getByRole('button'));
  210. userEvent.click(await screen.findByTestId('absolute'));
  211. expect(onChange).toHaveBeenLastCalledWith({
  212. relative: null,
  213. start: new Date('2017-10-02T22:41:20.000Z'),
  214. end: new Date('2017-10-16T22:41:20.000Z'),
  215. utc: true,
  216. });
  217. });
  218. it('switches from relative to absolute and then toggling UTC (starting with UTC)', async function () {
  219. renderComponent({
  220. relative: '7d',
  221. utc: true,
  222. });
  223. userEvent.click(screen.getByRole('button'));
  224. // Local time is 22:41:20-0500 -- this is what date picker should show
  225. userEvent.click(await screen.findByTestId('absolute'));
  226. expect(onChange).toHaveBeenCalledWith({
  227. relative: null,
  228. start: new Date('2017-10-09T22:41:20.000Z'),
  229. end: new Date('2017-10-16T22:41:20.000Z'),
  230. utc: true,
  231. });
  232. userEvent.click(screen.getByRole('checkbox'));
  233. expect(onChange).toHaveBeenLastCalledWith({
  234. relative: null,
  235. start: new Date('2017-10-09T22:41:20.000Z'),
  236. end: new Date('2017-10-16T22:41:20.000Z'),
  237. utc: false,
  238. });
  239. userEvent.click(screen.getByRole('checkbox'));
  240. expect(onChange).toHaveBeenLastCalledWith({
  241. relative: null,
  242. start: new Date('2017-10-10T02:41:20.000Z'),
  243. end: new Date('2017-10-17T02:41:20.000Z'),
  244. utc: true,
  245. });
  246. });
  247. it('switches from relative to absolute and then toggling UTC (starting with non-UTC)', async function () {
  248. renderComponent({
  249. relative: '7d',
  250. utc: false,
  251. });
  252. userEvent.click(screen.getByRole('button'));
  253. userEvent.click(await screen.findByTestId('absolute'));
  254. expect(onChange).toHaveBeenCalledWith({
  255. relative: null,
  256. start: new Date('2017-10-09T22:41:20.000-0400'),
  257. end: new Date('2017-10-16T22:41:20.000-0400'),
  258. utc: false,
  259. });
  260. userEvent.click(screen.getByRole('checkbox'));
  261. expect(onChange).toHaveBeenLastCalledWith({
  262. relative: null,
  263. start: new Date('2017-10-10T02:41:20.000Z'),
  264. end: new Date('2017-10-17T02:41:20.000Z'),
  265. utc: true,
  266. });
  267. userEvent.click(screen.getByRole('checkbox'));
  268. expect(onChange).toHaveBeenLastCalledWith({
  269. relative: null,
  270. start: new Date('2017-10-09T22:41:20.000Z'),
  271. end: new Date('2017-10-16T22:41:20.000Z'),
  272. utc: false,
  273. });
  274. });
  275. it('maintains time when switching UTC to local time', function () {
  276. // Times should never change when changing UTC option
  277. // Instead, the utc flagged is used when querying to create proper date
  278. let state;
  279. const {rerender} = renderComponent({
  280. relative: null,
  281. start: new Date('2017-10-10T00:00:00.000Z'),
  282. end: new Date('2017-10-17T23:59:59.000Z'),
  283. utc: true,
  284. });
  285. userEvent.click(screen.getByRole('button'));
  286. // Local
  287. userEvent.click(screen.getByRole('checkbox'));
  288. state = {
  289. relative: null,
  290. start: new Date('2017-10-10T00:00:00.000Z'),
  291. end: new Date('2017-10-17T23:59:59.000Z'),
  292. utc: false,
  293. };
  294. expect(onChange).toHaveBeenLastCalledWith(state);
  295. rerender(getComponent(state));
  296. // UTC
  297. userEvent.click(screen.getByRole('checkbox'));
  298. state = {
  299. relative: null,
  300. start: new Date('2017-10-10T00:00:00.000Z'),
  301. end: new Date('2017-10-17T23:59:59.000Z'),
  302. utc: true,
  303. };
  304. expect(onChange).toHaveBeenLastCalledWith(state);
  305. rerender(getComponent(state));
  306. // Local
  307. userEvent.click(screen.getByRole('checkbox'));
  308. expect(onChange).toHaveBeenLastCalledWith({
  309. relative: null,
  310. start: new Date('2017-10-10T00:00:00.000Z'),
  311. end: new Date('2017-10-17T23:59:59.000Z'),
  312. utc: false,
  313. });
  314. });
  315. it('deselects default filter when absolute date selected', async function () {
  316. renderComponent({
  317. relative: '14d',
  318. utc: false,
  319. });
  320. userEvent.click(screen.getByRole('button'));
  321. userEvent.click(await screen.findByTestId('absolute'));
  322. });
  323. it('uses the default absolute date', async function () {
  324. renderComponent({
  325. defaultAbsolute: {
  326. start: new Date('2017-10-10T00:00:00.000Z'),
  327. end: new Date('2017-10-17T23:59:59.000Z'),
  328. },
  329. });
  330. userEvent.click(screen.getByRole('button'));
  331. userEvent.click(await screen.findByTestId('absolute'));
  332. expect(onChange).toHaveBeenCalledWith({
  333. relative: null,
  334. start: new Date('2017-10-10T00:00:00.000Z'),
  335. end: new Date('2017-10-17T23:59:59.000Z'),
  336. });
  337. });
  338. it('uses the current absolute date if provided', async function () {
  339. renderComponent({
  340. start: new Date('2022-06-12T00:00:00.000Z'),
  341. end: new Date('2022-06-14T00:00:00.000Z'),
  342. });
  343. userEvent.click(screen.getByRole('button'));
  344. userEvent.click(await screen.findByTestId('absolute'));
  345. // On change should not be called because start/end did not change
  346. expect(onChange).not.toHaveBeenCalled();
  347. });
  348. it('can select arbitrary relative time ranges', () => {
  349. renderComponent();
  350. userEvent.click(screen.getByRole('button'));
  351. const input = screen.getByRole('textbox');
  352. userEvent.type(input, '5');
  353. // With just the number "5", all unit options should be present
  354. expect(screen.getByText('Last 5 seconds')).toBeInTheDocument();
  355. expect(screen.getByText('Last 5 minutes')).toBeInTheDocument();
  356. expect(screen.getByText('Last 5 hours')).toBeInTheDocument();
  357. expect(screen.getByText('Last 5 days')).toBeInTheDocument();
  358. expect(screen.getByText('Last 5 weeks')).toBeInTheDocument();
  359. userEvent.type(input, 'd');
  360. // With "5d", only "Last 5 days" should be shown
  361. expect(screen.getByText('Last 5 days')).toBeInTheDocument();
  362. expect(screen.queryByText('Last 5 seconds')).not.toBeInTheDocument();
  363. expect(screen.queryByText('Last 5 minutes')).not.toBeInTheDocument();
  364. expect(screen.queryByText('Last 5 hours')).not.toBeInTheDocument();
  365. expect(screen.queryByText('Last 5 weeks')).not.toBeInTheDocument();
  366. userEvent.type(input, 'ays');
  367. // "5days" Should still show "Last 5 days" option
  368. expect(screen.getByText('Last 5 days')).toBeInTheDocument();
  369. userEvent.type(input, '{Enter}');
  370. expect(onChange).toHaveBeenLastCalledWith({
  371. relative: '5d',
  372. start: undefined,
  373. end: undefined,
  374. });
  375. });
  376. it('cannot select arbitrary relative time ranges with disallowArbitraryRelativeRanges', () => {
  377. renderComponent({disallowArbitraryRelativeRanges: true});
  378. userEvent.click(screen.getByRole('button'));
  379. const input = screen.getByRole('textbox');
  380. userEvent.type(input, '5');
  381. expect(screen.getByText('No items found')).toBeInTheDocument();
  382. userEvent.type(input, '{Enter}');
  383. expect(onChange).not.toHaveBeenCalled();
  384. });
  385. });