test(cron): add UTC timestamp tests and fix formatDate consistency

- CronJobTableCell.test.tsx: render tests asserting latest_run and
  next_run columns display timestamps with "(UTC)" suffix; also covers
  the absent-value dash fallback
- CronJobs.utils.test.ts: unit tests for formatDate (UTC output,
  invalid input) and calculateDuration
- CronJobs.utils.tsx: add timeZone: 'UTC' to formatDate so the
  fallback path is consistent with the primary TimestampInfo display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jordi EnricandClaude Sonnet 4.6 committed 2026-06-15 12:08:27 +02:00
1 parent 3c997edf0c
commit 80d5b366b7
3 files changed
+91 -25

No files matched your search

@@ -470,17 +470,20 @@ describe('formatDate', () => {
expect(formatDate('not-a-date')).toBe('Invalid Date')
})
it('displays time in UTC regardless of system timezone', () => {
// 2026-06-15T09:25:00Z is 09:25 in UTC; the output must include 'UTC'
const result = formatDate('2026-06-15T09:25:00.000Z')
expect(result).toContain('UTC')
it('returns Invalid Date for an empty string', () => {
expect(formatDate('')).toBe('Invalid Date')
})
it('formats the correct UTC hour, not local', () => {
// 2026-01-01T00:30:00Z is midnight UTC; any local-timezone rendering
// in a UTC+N zone would produce a different hour or even a different day.
const result = formatDate('2026-01-01T00:30:00.000Z')
expect(result).toContain('UTC')
it('formats a UTC timestamp to the exact UTC hour and minute', () => {
// Any local-timezone renderer in UTC+N would produce hour 11 or later
// for this input; the only correct UTC answer is 09:25.
expect(formatDate('2026-06-15T09:25:00.000Z')).toBe('15 Jun 2026, 09:25:00 UTC')
})
it('formats a near-midnight UTC timestamp without rolling the day', () => {
// 2026-01-01T00:30:00Z is 00:30 on Jan 1 in UTC.
// A UTC+5 renderer would produce Dec 31 at 19:30 — the wrong day entirely.
expect(formatDate('2026-01-01T00:30:00.000Z')).toBe('01 Jan 2026, 00:30:00 UTC')
})
})
@@ -1,8 +1,12 @@
import { keyword, literal, safeSql, type SafeSqlFragment } from '@supabase/pg-meta/src/pg-format'
import { toString as CronToString } from 'cronstrue'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { Column } from 'react-data-grid'
import { cn } from 'ui'
dayjs.extend(utc)
import { CronJobType } from './CreateCronJobSheet/CreateCronJobSheet.constants'
import { CRON_TABLE_COLUMNS, HTTPHeader, secondsPattern } from './CronJobs.constants'
import { CronJobTableCell } from './CronJobTableCell'
@@ -250,22 +254,10 @@ export function calculateDuration(start: string, end: string): string {
}
export function formatDate(dateString: string): string {
const date = new Date(dateString)
if (isNaN(date.getTime())) {
return 'Invalid Date'
}
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'UTC',
timeZoneName: 'short',
}
return date.toLocaleString(undefined, options)
if (!dateString) return 'Invalid Date'
const parsed = dayjs.utc(dateString)
if (!parsed.isValid()) return 'Invalid Date'
return parsed.format('DD MMM YYYY, HH:mm:ss [UTC]')
}
export function isSecondsFormat(schedule: string): boolean {
@@ -0,0 +1,71 @@
import { screen } from '@testing-library/dom'
import { describe, expect, it, vi } from 'vitest'
import { render } from '@/tests/helpers'
import { PreviousRunsTab } from './PreviousRunsTab'
const TEST_START = '2026-06-15T09:25:00.000Z'
const TEST_END = '2026-06-15T09:25:05.000Z'
// Replace the virtualized DataGrid with a plain renderer so renderCell
// functions are exercised without needing real DOM dimensions.
vi.mock('react-data-grid', () => ({
default: ({ rows, columns }: any) => (
<div>
{rows.map((row: any, rowIdx: number) =>
columns.map((col: any) => (
<div key={`${rowIdx}-${String(col.key)}`}>{col.renderCell?.({ row })}</div>
))
)}
</div>
),
Row: ({ children }: any) => <>{children}</>,
}))
vi.mock('common', () => ({
useParams: () => ({ childId: '1' }),
}))
vi.mock('@/hooks/misc/useSelectedProject', () => ({
useSelectedProjectQuery: () => ({
data: { ref: 'test-ref', connectionString: 'test-conn' },
}),
}))
vi.mock('@/data/database-cron-jobs/database-cron-jobs-runs-infinite-query', () => ({
useCronJobRunsInfiniteQuery: () => ({
data: {
pages: [
[
{
runid: 1,
job_pid: 123,
jobid: 1,
start_time: TEST_START,
end_time: TEST_END,
return_message: null,
status: 'succeeded',
},
],
],
},
isPending: false,
isFetching: false,
isFetchingNextPage: false,
hasNextPage: false,
fetchNextPage: vi.fn(),
}),
}))
vi.mock('@/hooks/misc/useInfiniteScroll', () => ({
useInfiniteScroll: () => vi.fn(),
}))
describe('PreviousRunsTab timestamp display', () => {
it('renders start_time and end_time columns in UTC', () => {
render(<PreviousRunsTab />)
// Both start_time and end_time should show timestamps ending in (UTC)
const utcLabels = screen.getAllByText(/\(UTC\)/)
expect(utcLabels.length).toBeGreaterThanOrEqual(2)
})
})