Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/core/src/lib/__tests__/sortByDate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,39 @@ describe('sortByDate', () => {
})
})

describe('handling unparseable dates', () => {
// getTime() returns NaN for unparseable strings, which must not leak into
// the comparator (NaN result = unspecified order per the sort contract).
// NaN coerces to epoch 0, so unparseable dates sit between dateless and
// real post-1970 dates (issue #29).
it.each<{ order: SortOrder; expected: string[] }>([
{ order: 'desc', expected: ['2', '1', '3'] },
{ order: 'asc', expected: ['3', '1', '2'] },
])('should treat unparseable date strings as epoch instead of poisoning the sort in $order order', ({ order, expected }) => {
const projects = [
createProject({ id: '1', updatedAt: 'not-a-date' }),
createProject({ id: '2', updatedAt: '2024-06-01' }),
createProject({ id: '3', updatedAt: null, createdAt: null }),
]

const result = sortByDate(projects, order)

expect(result.map(p => p.id)).toEqual(expected)
})

it('should keep a deterministic order when every date is unparseable', () => {
const projects = [
createProject({ id: '1', updatedAt: 'not-a-date' }),
createProject({ id: '2', updatedAt: 'also-not-a-date' }),
]

const result = sortByDate(projects)

// Equal (epoch) comparisons keep input order via stable sort
expect(result.map(p => p.id)).toEqual(['1', '2'])
})
})

describe('date parsing', () => {
it('should correctly parse ISO date strings', () => {
const projects = [
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/lib/sortByDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ export function sortByDate(projects: ProjexProject[], order: SortOrder = 'desc')

// Treat missing dates as older than any real date so dateless projects sort
// last in newest-first order and first in oldest-first order, consistently
// in both directions (issue #23).
const timeA = dateA ? new Date(dateA).getTime() : Number.NEGATIVE_INFINITY
const timeB = dateB ? new Date(dateB).getTime() : Number.NEGATIVE_INFINITY
// in both directions (issue #23). Unparseable date strings yield NaN from
// getTime(), so coerce to epoch 0 to keep the comparator NaN-free and the
// order deterministic (issue #29).
const timeA = dateA ? new Date(dateA).getTime() || 0 : Number.NEGATIVE_INFINITY
const timeB = dateB ? new Date(dateB).getTime() || 0 : Number.NEGATIVE_INFINITY

return (timeA - timeB) * multiplier
})
Expand Down