diff --git a/packages/core/src/lib/__tests__/sortByDate.test.ts b/packages/core/src/lib/__tests__/sortByDate.test.ts index 9c9bcee..30dc6c0 100644 --- a/packages/core/src/lib/__tests__/sortByDate.test.ts +++ b/packages/core/src/lib/__tests__/sortByDate.test.ts @@ -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 = [ diff --git a/packages/core/src/lib/sortByDate.ts b/packages/core/src/lib/sortByDate.ts index f32a66a..1fe506a 100644 --- a/packages/core/src/lib/sortByDate.ts +++ b/packages/core/src/lib/sortByDate.ts @@ -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 })