Logs.Datepickers.test.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { screen, within } from '@testing-library/react'
  2. import userEvent from '@testing-library/user-event'
  3. import dayjs from 'dayjs'
  4. import timezone from 'dayjs/plugin/timezone'
  5. import utc from 'dayjs/plugin/utc'
  6. import { describe, expect, test, vi } from 'vitest'
  7. import { render } from '../../helpers'
  8. import { PREVIEWER_DATEPICKER_HELPERS } from '@/components/interfaces/Settings/Logs/Logs.constants'
  9. import {
  10. generateDynamicHelper,
  11. generateDynamicHelpers,
  12. generateHelpersFromInput,
  13. LogsDatePicker,
  14. parseCustomInput,
  15. } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
  16. import { DatetimeHelper } from '@/components/interfaces/Settings/Logs/Logs.types'
  17. dayjs.extend(timezone)
  18. dayjs.extend(utc)
  19. describe('parseCustomInput', () => {
  20. test('returns invalid for empty input', () => {
  21. expect(parseCustomInput('')).toEqual({ type: 'invalid' })
  22. expect(parseCustomInput(' ')).toEqual({ type: 'invalid' })
  23. })
  24. test('parses number only input', () => {
  25. expect(parseCustomInput('25')).toEqual({ type: 'number', value: 25 })
  26. expect(parseCustomInput(' 10 ')).toEqual({ type: 'number', value: 10 })
  27. })
  28. test('parses number with unit letter', () => {
  29. expect(parseCustomInput('2h')).toEqual({ type: 'unit', value: 2, unit: 'hour' })
  30. expect(parseCustomInput('30m')).toEqual({ type: 'unit', value: 30, unit: 'minute' })
  31. expect(parseCustomInput('7d')).toEqual({ type: 'unit', value: 7, unit: 'day' })
  32. })
  33. test('parses number with space and unit letter', () => {
  34. expect(parseCustomInput('2 h')).toEqual({ type: 'unit', value: 2, unit: 'hour' })
  35. expect(parseCustomInput('30 m')).toEqual({ type: 'unit', value: 30, unit: 'minute' })
  36. expect(parseCustomInput('7 d')).toEqual({ type: 'unit', value: 7, unit: 'day' })
  37. })
  38. test('parses number with full unit name prefix', () => {
  39. expect(parseCustomInput('2hour')).toEqual({ type: 'unit', value: 2, unit: 'hour' })
  40. expect(parseCustomInput('2hours')).toEqual({ type: 'invalid' })
  41. expect(parseCustomInput('4day')).toEqual({ type: 'unit', value: 4, unit: 'day' })
  42. expect(parseCustomInput('4days')).toEqual({ type: 'invalid' })
  43. expect(parseCustomInput('30min')).toEqual({ type: 'unit', value: 30, unit: 'minute' })
  44. expect(parseCustomInput('30minute')).toEqual({ type: 'unit', value: 30, unit: 'minute' })
  45. })
  46. test('is case insensitive', () => {
  47. expect(parseCustomInput('2H')).toEqual({ type: 'unit', value: 2, unit: 'hour' })
  48. expect(parseCustomInput('30M')).toEqual({ type: 'unit', value: 30, unit: 'minute' })
  49. expect(parseCustomInput('7D')).toEqual({ type: 'unit', value: 7, unit: 'day' })
  50. })
  51. test('returns invalid for non-matching unit', () => {
  52. expect(parseCustomInput('2x')).toEqual({ type: 'invalid' })
  53. expect(parseCustomInput('2yoie')).toEqual({ type: 'invalid' })
  54. expect(parseCustomInput('abc')).toEqual({ type: 'invalid' })
  55. })
  56. test('returns invalid for zero or negative', () => {
  57. expect(parseCustomInput('0')).toEqual({ type: 'invalid' })
  58. expect(parseCustomInput('-5')).toEqual({ type: 'invalid' })
  59. })
  60. })
  61. describe('generateDynamicHelper', () => {
  62. test('generates helper with correct text', () => {
  63. const helper = generateDynamicHelper(5, 'hour')
  64. expect(helper.text).toBe('Last 5 hours')
  65. })
  66. test('uses singular form when value is 1', () => {
  67. expect(generateDynamicHelper(1, 'minute').text).toBe('Last 1 minute')
  68. expect(generateDynamicHelper(1, 'hour').text).toBe('Last 1 hour')
  69. expect(generateDynamicHelper(1, 'day').text).toBe('Last 1 day')
  70. })
  71. test('uses plural form when value > 1', () => {
  72. expect(generateDynamicHelper(2, 'minute').text).toBe('Last 2 minutes')
  73. expect(generateDynamicHelper(2, 'hour').text).toBe('Last 2 hours')
  74. expect(generateDynamicHelper(2, 'day').text).toBe('Last 2 days')
  75. })
  76. test('calcFrom returns correct ISO string', () => {
  77. const helper = generateDynamicHelper(1, 'hour')
  78. const from = dayjs(helper.calcFrom())
  79. const expectedFrom = dayjs().subtract(1, 'hour')
  80. expect(from.diff(expectedFrom, 'second')).toBeLessThan(2)
  81. })
  82. })
  83. describe('generateDynamicHelpers', () => {
  84. test('generates 3 helpers for minutes, hours, days', () => {
  85. const helpers = generateDynamicHelpers(5)
  86. expect(helpers).toHaveLength(3)
  87. expect(helpers[0].text).toBe('Last 5 minutes')
  88. expect(helpers[1].text).toBe('Last 5 hours')
  89. expect(helpers[2].text).toBe('Last 5 days')
  90. })
  91. })
  92. describe('generateHelpersFromInput', () => {
  93. test('returns null for invalid input', () => {
  94. expect(generateHelpersFromInput('')).toBeNull()
  95. expect(generateHelpersFromInput('abc')).toBeNull()
  96. expect(generateHelpersFromInput('2yoie')).toBeNull()
  97. })
  98. test('returns 3 helpers for number only input', () => {
  99. const helpers = generateHelpersFromInput('25')
  100. expect(helpers).toHaveLength(3)
  101. expect(helpers![0].text).toBe('Last 25 minutes')
  102. expect(helpers![1].text).toBe('Last 25 hours')
  103. expect(helpers![2].text).toBe('Last 25 days')
  104. })
  105. test('returns single helper for unit input', () => {
  106. const helpers = generateHelpersFromInput('2h')
  107. expect(helpers).toHaveLength(1)
  108. expect(helpers![0].text).toBe('Last 2 hours')
  109. })
  110. })
  111. const mockFn = vi.fn()
  112. test('renders warning', async () => {
  113. const from = dayjs().subtract(10, 'days')
  114. const to = dayjs()
  115. render(
  116. <LogsDatePicker
  117. helpers={[]}
  118. onSubmit={mockFn}
  119. value={{
  120. from: from.toISOString(),
  121. to: to.toISOString(),
  122. }}
  123. />
  124. )
  125. await userEvent.click(await screen.findByText(RegExp(from.format('DD MMM'))))
  126. await screen.findByText(/memory errors/)
  127. await screen.findByText(RegExp(from.format('DD MMM')))
  128. })
  129. test('renders dates in local time', async () => {
  130. const from = dayjs().subtract(1, 'days')
  131. const to = dayjs()
  132. render(
  133. <LogsDatePicker
  134. helpers={PREVIEWER_DATEPICKER_HELPERS}
  135. onSubmit={mockFn}
  136. value={{
  137. from: from.toISOString(),
  138. to: to.toISOString(),
  139. }}
  140. />
  141. )
  142. // renders time locally
  143. await userEvent.click(await screen.findByText(RegExp(from.format('DD MMM'))))
  144. await screen.findByText(RegExp(from.format('DD MMM')))
  145. })
  146. test('renders datepicker selected dates in local time', async () => {
  147. const from = dayjs().date(25)
  148. const to = dayjs().date(27)
  149. render(
  150. <LogsDatePicker
  151. helpers={PREVIEWER_DATEPICKER_HELPERS}
  152. value={{
  153. from: from.toISOString(),
  154. to: to.toISOString(),
  155. }}
  156. onSubmit={mockFn}
  157. />
  158. )
  159. // renders time locally
  160. await userEvent.click(await screen.findByText(RegExp(from.format('DD MMM'))))
  161. // inputs with local time
  162. await screen.findByText(
  163. `${from.format('DD MMM')}, ${from.format('HH:mm')} - ${to.format('DD MMM')}, ${to.format('HH:mm')}`
  164. )
  165. // selected date should be in local time
  166. await screen.findByText('25', { selector: "*[aria-label*='selected'" })
  167. await screen.findByText('27', { selector: "*[aria-label*='selected'" })
  168. })
  169. test('datepicker onSubmit will return ISO string of selected dates', async () => {
  170. const mockFn = vi.fn()
  171. const todayAt1300 = dayjs().hour(13).minute(0).second(0).millisecond(0).toISOString()
  172. const todayAt2359 = dayjs().hour(23).minute(59).second(59).millisecond(0).toISOString()
  173. render(
  174. <LogsDatePicker
  175. helpers={PREVIEWER_DATEPICKER_HELPERS}
  176. value={{
  177. from: todayAt1300,
  178. to: todayAt2359,
  179. }}
  180. onSubmit={mockFn}
  181. />
  182. )
  183. // open the datepicker
  184. userEvent.click(screen.getByText(/13:00/i))
  185. const day15 = dayjs().date(15)
  186. const day16 = day15.add(1, 'day')
  187. // Find and click on first date
  188. const day15Element = await screen.findByText(day15.format('D'))
  189. userEvent.dblClick(day15Element)
  190. // Find and click on second date
  191. const day16Element = await screen.findByText(day16.format('D'))
  192. userEvent.click(day16Element)
  193. await userEvent.click(await screen.findByText('Apply'))
  194. expect(mockFn).toBeCalled()
  195. const call = mockFn.mock.calls[0][0]
  196. expect(call).toMatchObject({
  197. from: dayjs().date(day15.date()).hour(13).minute(0).second(0).millisecond(0).toISOString(),
  198. to: dayjs().date(day16.date()).hour(23).minute(59).second(59).millisecond(0).toISOString(),
  199. })
  200. })
  201. test('disabled helpers are disabled', async () => {
  202. const helpers: DatetimeHelper[] = [
  203. {
  204. text: 'Last 7 days',
  205. calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(),
  206. calcTo: () => '',
  207. },
  208. {
  209. text: 'Last 30 days',
  210. calcFrom: () => dayjs().subtract(30, 'day').startOf('day').toISOString(),
  211. calcTo: () => '',
  212. disabled: true,
  213. },
  214. ]
  215. render(
  216. <LogsDatePicker
  217. helpers={helpers}
  218. onSubmit={mockFn}
  219. value={{
  220. from: dayjs().subtract(7, 'day').startOf('day').toISOString(),
  221. to: '',
  222. isHelper: true,
  223. text: 'Last 7 days',
  224. }}
  225. />
  226. )
  227. // click the datepicker
  228. userEvent.click(screen.getByText('Last 7 days'))
  229. const disabledHelperContainer = await screen.findByText(/last 30 days/i)
  230. const disabledButton = within(disabledHelperContainer).getByRole('radio', {
  231. hidden: true,
  232. })
  233. expect(disabledButton.getAttribute('aria-disabled')).toBe('true')
  234. })
  235. test('passing a value prop shows the correct dates in the label', async () => {
  236. const from = dayjs().subtract(10, 'days')
  237. const to = dayjs()
  238. render(
  239. <LogsDatePicker
  240. helpers={[]}
  241. value={{ from: from.toISOString(), to: to.toISOString() }}
  242. onSubmit={mockFn}
  243. />
  244. )
  245. await screen.findByText(
  246. `${from.format('DD MMM')}, ${from.format('HH:mm')} - ${to.format('DD MMM')}, ${to.format('HH:mm')}`
  247. )
  248. // change the date
  249. userEvent.click(await screen.findByText(RegExp(from.format('DD MMM'))))
  250. userEvent.click(await screen.findByText(RegExp(to.format('DD MMM'))))
  251. await screen.findByText(
  252. `${from.format('DD MMM')}, ${from.format('HH:mm')} - ${to.format('DD MMM')}, ${to.format('HH:mm')}`
  253. )
  254. })
  255. test('passing a helper as a value prop shows the helper text in the label', async () => {
  256. const helper = {
  257. text: 'Last 7 days',
  258. calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(),
  259. calcTo: () => '',
  260. }
  261. render(
  262. <LogsDatePicker
  263. helpers={[helper]}
  264. value={{
  265. from: helper.calcFrom(),
  266. to: helper.calcTo(),
  267. isHelper: true,
  268. text: helper.text,
  269. }}
  270. onSubmit={mockFn}
  271. />
  272. )
  273. await screen.findByText(helper.text)
  274. })