index.spec.tsx 12 KB

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