Stop re-parsing dates in isStatusStillActive (#11574)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,6 @@ import {moderateStatus} from '@bsky/sdk/moderation'
|
|||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
import {isAfter, parseISO} from 'date-fns'
|
|
||||||
|
|
||||||
import {uploadBlob} from '#/lib/api'
|
import {uploadBlob} from '#/lib/api'
|
||||||
import {imageToThumb} from '#/lib/api/resolve'
|
import {imageToThumb} from '#/lib/api/resolve'
|
||||||
@@ -23,7 +22,11 @@ import {useTickEveryMinute} from '#/state/shell'
|
|||||||
import {useDialogContext} from '#/components/Dialog'
|
import {useDialogContext} from '#/components/Dialog'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {getLiveNowHost, getLiveServiceNames} from '#/features/liveNow/utils'
|
import {
|
||||||
|
getLiveNowHost,
|
||||||
|
getLiveServiceNames,
|
||||||
|
isStatusStillActive,
|
||||||
|
} from '#/features/liveNow/utils'
|
||||||
import {app, com} from '#/lexicons'
|
import {app, com} from '#/lexicons'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
@@ -151,14 +154,6 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
|||||||
}, [shadowed, config, tick, moderation])
|
}, [shadowed, config, tick, moderation])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStatusStillActive(timeStr: string | undefined) {
|
|
||||||
if (!timeStr) return false
|
|
||||||
const now = new Date()
|
|
||||||
const expiry = parseISO(timeStr)
|
|
||||||
|
|
||||||
return isAfter(expiry, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates whether the live status is valid for display in the app. Does NOT
|
* Validates whether the live status is valid for display in the app. Does NOT
|
||||||
* validate if the status is valid for the acting user e.g. as they go live.
|
* validate if the status is valid for the acting user e.g. as they go live.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {isStatusStillActive} from '#/features/liveNow/utils'
|
||||||
|
|
||||||
|
describe('isStatusStillActive', () => {
|
||||||
|
const iso = (offsetMs: number) =>
|
||||||
|
new Date(Date.now() + offsetMs).toISOString()
|
||||||
|
|
||||||
|
it('is active for a future expiry', () => {
|
||||||
|
expect(isStatusStillActive(iso(60_000))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not active for a past expiry', () => {
|
||||||
|
expect(isStatusStillActive(iso(-60_000))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not active without a value', () => {
|
||||||
|
expect(isStatusStillActive(undefined)).toBe(false)
|
||||||
|
expect(isStatusStillActive('')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The predicate reads an atproto `datetime`, which always carries an offset.
|
||||||
|
* An unparseable value yields NaN, and every NaN comparison is false, so a
|
||||||
|
* bad timestamp reads as "not live" rather than throwing or showing a stale
|
||||||
|
* live badge.
|
||||||
|
*/
|
||||||
|
it('is not active for an unparseable value', () => {
|
||||||
|
expect(isStatusStillActive('not-a-date')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles offsets and fractional seconds', () => {
|
||||||
|
const future = new Date(Date.now() + 3_600_000)
|
||||||
|
expect(isStatusStillActive(future.toISOString())).toBe(true)
|
||||||
|
expect(isStatusStillActive('2000-01-01T00:00:00.123+00:00')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -97,3 +97,16 @@ export function getLiveNowHost(url: string) {
|
|||||||
const {hostname} = new URL(url)
|
const {hostname} = new URL(url)
|
||||||
return sanitizeLiveNowHost(hostname)
|
return sanitizeLiveNowHost(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a live status has not yet expired.
|
||||||
|
*
|
||||||
|
* Called per post, per render while scrolling, so this stays on primitives -
|
||||||
|
* `parseISO` plus two `Date` objects showed up in the scroll profile.
|
||||||
|
* `Date.parse` returns `NaN` for an unparseable value and `NaN > n` is `false`,
|
||||||
|
* which is the same "not active" answer the date-fns version gave.
|
||||||
|
*/
|
||||||
|
export function isStatusStillActive(timeStr: string | undefined) {
|
||||||
|
if (!timeStr) return false
|
||||||
|
return Date.parse(timeStr) > Date.now()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user