mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Context As per PR title: recently updated list just comprises of notebooks and chats Note: known API issue that save a notebook doesn't update its `updated_at` value, so you'll notice that if you save a notebook it doesn't move up the recently updated list <img width="281" height="270" alt="image" src="https://github.com/user-attachments/assets/f637a593-09a7-4df4-85b7-43637f04738d" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a home view displaying recently updated notebooks and chats. * Recent items are sorted by update time and limited to five entries. * Added relative timestamps in minutes, hours, or days. * Chat entries open directly, while notebook entries link to their explorer view. * Added an empty state when no recent items are available. * **Bug Fixes** * Excluded unsupported chat types from recent-item results. * Improved handling of unavailable timestamps. * **Tests** * Added coverage for sorting, filtering, limits, and relative-time formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { formatRelativeTimeShort, getRecentlyUpdatedItems } from './ExplorerNavHome.utils'
|
|
import type { NotebookRow } from '@/data/content/notebooks/notebooks-infinite-query'
|
|
import type { ChatSession } from '@/state/ai-assistant-state'
|
|
|
|
const notebook = (overrides: Partial<NotebookRow> = {}): NotebookRow =>
|
|
({
|
|
id: 'notebook-1',
|
|
name: 'Signup funnel',
|
|
updated_at: '2026-01-01T00:00:00.000Z',
|
|
...overrides,
|
|
}) as NotebookRow
|
|
|
|
const chat = (overrides: Partial<ChatSession> = {}): ChatSession =>
|
|
({
|
|
id: 'chat-1',
|
|
name: 'Investigate errors',
|
|
messages: [],
|
|
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
...overrides,
|
|
}) as ChatSession
|
|
|
|
describe('getRecentlyUpdatedItems', () => {
|
|
it('merges notebooks and chats sorted by recency, most recent first', () => {
|
|
const notebooks = [notebook({ id: 'n1', updated_at: '2026-01-01T00:00:00.000Z' })]
|
|
const chats = [chat({ id: 'c1', updatedAt: new Date('2026-02-01T00:00:00.000Z') })]
|
|
|
|
expect(getRecentlyUpdatedItems({ notebooks, chats }).map((item) => item.id)).toEqual([
|
|
'c1',
|
|
'n1',
|
|
])
|
|
})
|
|
|
|
it('filters out support chats', () => {
|
|
const chats = [
|
|
chat({
|
|
id: 'support',
|
|
supportMetadata: { isSupportChat: true } as ChatSession['supportMetadata'],
|
|
}),
|
|
]
|
|
|
|
expect(getRecentlyUpdatedItems({ notebooks: [], chats })).toEqual([])
|
|
})
|
|
|
|
it('sorts a chat with no updatedAt yet (not hydrated) after everything else', () => {
|
|
const chats = [
|
|
chat({ id: 'rehydrating', updatedAt: undefined as unknown as Date }),
|
|
chat({ id: 'recent', updatedAt: new Date('2026-01-01T00:00:00.000Z') }),
|
|
]
|
|
|
|
expect(getRecentlyUpdatedItems({ notebooks: [], chats }).map((item) => item.id)).toEqual([
|
|
'recent',
|
|
'rehydrating',
|
|
])
|
|
})
|
|
|
|
it('truncates to the given limit', () => {
|
|
const notebooks = Array.from({ length: 10 }, (_, i) =>
|
|
notebook({ id: `n${i}`, updated_at: new Date(2026, 0, i + 1).toISOString() })
|
|
)
|
|
|
|
const result = getRecentlyUpdatedItems({ notebooks, chats: [], limit: 3 })
|
|
|
|
expect(result).toHaveLength(3)
|
|
expect(result.map((item) => item.id)).toEqual(['n9', 'n8', 'n7'])
|
|
})
|
|
|
|
it('defaults to a limit of 5', () => {
|
|
const notebooks = Array.from({ length: 10 }, (_, i) =>
|
|
notebook({ id: `n${i}`, updated_at: new Date(2026, 0, i + 1).toISOString() })
|
|
)
|
|
|
|
expect(getRecentlyUpdatedItems({ notebooks, chats: [] })).toHaveLength(5)
|
|
})
|
|
})
|
|
|
|
describe('formatRelativeTimeShort', () => {
|
|
const now = new Date('2026-01-01T12:00:00.000Z').getTime()
|
|
|
|
it('shows minutes under an hour', () => {
|
|
const timestamp = now - 12 * 60 * 1000
|
|
expect(formatRelativeTimeShort(timestamp, now)).toBe('12m ago')
|
|
})
|
|
|
|
it('shows "just now" for under a minute', () => {
|
|
const timestamp = now - 30 * 1000
|
|
expect(formatRelativeTimeShort(timestamp, now)).toBe('just now')
|
|
})
|
|
|
|
it('shows hours under a day', () => {
|
|
const timestamp = now - 21 * 60 * 60 * 1000
|
|
expect(formatRelativeTimeShort(timestamp, now)).toBe('21h ago')
|
|
})
|
|
|
|
it('shows days beyond a day', () => {
|
|
const timestamp = now - 2 * 24 * 60 * 60 * 1000
|
|
expect(formatRelativeTimeShort(timestamp, now)).toBe('2d ago')
|
|
})
|
|
})
|