## Registro de Uso - Nuevo modelo NoteUsage en Prisma - Tipos de eventos: view, search_click, related_click, link_click, copy_command, copy_snippet - Funciones: trackNoteUsage, getUsageStats, getRecentlyUsedNotes - localStorage: recentlyViewed (últimas 10 notas) - Rastreo de copias en markdown-content.tsx ## Dashboard Rediseñado - 5 bloques: Recientes, Más usadas, Comandos recientes, Snippets recientes, Según actividad - Nuevo src/lib/dashboard.ts con getDashboardData() - Recomendaciones basadas en recentlyViewed ## Scoring con Uso Real - search.ts: +1 per 5 views (max +3), +2 recency boost - related.ts: mismo sistema de usage boost - No eclipsa match textual fuerte ## Tests - 110 tests pasando (usage, dashboard, related, search) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
294 lines
10 KiB
TypeScript
294 lines
10 KiB
TypeScript
import { getDashboardData, hasVisibleBlocks, DashboardNote, DashboardData } from '@/lib/dashboard'
|
|
|
|
// Mock prisma and usage before importing dashboard module
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
note: {
|
|
findMany: jest.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
jest.mock('@/lib/usage', () => ({
|
|
getRecentlyUsedNotes: jest.fn(),
|
|
}))
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
import { getRecentlyUsedNotes } from '@/lib/usage'
|
|
|
|
describe('dashboard.ts', () => {
|
|
const mockNotes = [
|
|
{
|
|
id: 'note-1',
|
|
title: 'Docker Commands',
|
|
content: 'docker build, docker run',
|
|
type: 'command',
|
|
isFavorite: true,
|
|
isPinned: false,
|
|
createdAt: new Date('2024-01-01'),
|
|
updatedAt: new Date('2024-01-15'),
|
|
tags: [{ tag: { id: 'tag-1', name: 'docker' } }],
|
|
},
|
|
{
|
|
id: 'note-2',
|
|
title: 'React Snippet',
|
|
content: 'useState usage',
|
|
type: 'snippet',
|
|
isFavorite: false,
|
|
isPinned: true,
|
|
createdAt: new Date('2024-01-02'),
|
|
updatedAt: new Date('2024-01-14'),
|
|
tags: [{ tag: { id: 'tag-2', name: 'react' } }],
|
|
},
|
|
{
|
|
id: 'note-3',
|
|
title: 'Git Commands',
|
|
content: 'git commit, git push',
|
|
type: 'command',
|
|
isFavorite: false,
|
|
isPinned: false,
|
|
createdAt: new Date('2024-01-03'),
|
|
updatedAt: new Date('2024-01-13'),
|
|
tags: [{ tag: { id: 'tag-3', name: 'git' } }],
|
|
},
|
|
{
|
|
id: 'note-4',
|
|
title: 'SQL Queries',
|
|
content: 'SELECT * FROM',
|
|
type: 'snippet',
|
|
isFavorite: true,
|
|
isPinned: true,
|
|
createdAt: new Date('2024-01-04'),
|
|
updatedAt: new Date('2024-01-12'),
|
|
tags: [{ tag: { id: 'tag-4', name: 'sql' } }],
|
|
},
|
|
]
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe('getDashboardData', () => {
|
|
it('should return dashboard data with all sections', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-1', count: 5, lastUsed: new Date() },
|
|
{ noteId: 'note-2', count: 3, lastUsed: new Date() },
|
|
])
|
|
|
|
const result = await getDashboardData(3)
|
|
|
|
expect(result).toHaveProperty('recentNotes')
|
|
expect(result).toHaveProperty('mostUsedNotes')
|
|
expect(result).toHaveProperty('recentCommands')
|
|
expect(result).toHaveProperty('recentSnippets')
|
|
expect(result).toHaveProperty('activityBasedNotes')
|
|
expect(result).toHaveProperty('hasActivity')
|
|
})
|
|
|
|
it('should return recent notes ordered by updatedAt', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
|
|
const result = await getDashboardData(2)
|
|
|
|
expect(result.recentNotes).toHaveLength(2)
|
|
expect(result.recentNotes[0].id).toBe('note-1') // most recent
|
|
expect(result.recentNotes[1].id).toBe('note-2')
|
|
})
|
|
|
|
it('should return most used notes by usage count', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-2', count: 10, lastUsed: new Date() },
|
|
{ noteId: 'note-1', count: 5, lastUsed: new Date() },
|
|
])
|
|
|
|
const result = await getDashboardData(3)
|
|
|
|
expect(result.mostUsedNotes).toHaveLength(2)
|
|
expect(result.mostUsedNotes[0].id).toBe('note-2') // highest usage
|
|
expect(result.mostUsedNotes[0].usageCount).toBe(10)
|
|
})
|
|
|
|
it('should return only command type notes for recentCommands', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
|
|
const result = await getDashboardData(5)
|
|
|
|
expect(result.recentCommands).toHaveLength(2)
|
|
expect(result.recentCommands.every((n: DashboardNote) => n.type === 'command')).toBe(true)
|
|
expect(result.recentCommands[0].id).toBe('note-1')
|
|
})
|
|
|
|
it('should return only snippet type notes for recentSnippets', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
|
|
const result = await getDashboardData(5)
|
|
|
|
expect(result.recentSnippets).toHaveLength(2)
|
|
expect(result.recentSnippets.every((n: DashboardNote) => n.type === 'snippet')).toBe(true)
|
|
})
|
|
|
|
it('should return activity based notes excluding recent notes', async () => {
|
|
// mockNotes are sorted by updatedAt desc: note-1, note-2, note-3, note-4
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
// note-4 is in recently used but will be filtered out since recentNotes has first 3
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-4', count: 5, lastUsed: new Date() },
|
|
])
|
|
|
|
const result = await getDashboardData(3)
|
|
|
|
// note-4 is used but not in recentNotes (which contains note-1, note-2, note-3)
|
|
expect(result.activityBasedNotes).toHaveLength(1)
|
|
expect(result.activityBasedNotes[0].id).toBe('note-4')
|
|
})
|
|
|
|
it('should set hasActivity based on recently used notes', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-1', count: 5, lastUsed: new Date() },
|
|
])
|
|
|
|
const resultWithActivity = await getDashboardData(3)
|
|
expect(resultWithActivity.hasActivity).toBe(true)
|
|
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
const resultWithoutActivity = await getDashboardData(3)
|
|
expect(resultWithoutActivity.hasActivity).toBe(false)
|
|
})
|
|
|
|
it('should respect the limit parameter', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-1', count: 10, lastUsed: new Date() },
|
|
{ noteId: 'note-2', count: 8, lastUsed: new Date() },
|
|
{ noteId: 'note-3', count: 6, lastUsed: new Date() },
|
|
{ noteId: 'note-4', count: 4, lastUsed: new Date() },
|
|
])
|
|
|
|
const result = await getDashboardData(2)
|
|
|
|
expect(result.recentNotes).toHaveLength(2)
|
|
expect(result.mostUsedNotes).toHaveLength(2)
|
|
})
|
|
|
|
it('should add usageCount to notes from usage map', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([
|
|
{ noteId: 'note-1', count: 10, lastUsed: new Date() },
|
|
{ noteId: 'note-2', count: 5, lastUsed: new Date() },
|
|
])
|
|
|
|
const result = await getDashboardData(3)
|
|
|
|
const note1 = result.mostUsedNotes.find((n: DashboardNote) => n.id === 'note-1')
|
|
const note2 = result.mostUsedNotes.find((n: DashboardNote) => n.id === 'note-2')
|
|
expect(note1?.usageCount).toBe(10)
|
|
expect(note2?.usageCount).toBe(5)
|
|
})
|
|
|
|
it('should return empty arrays when no notes exist', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue([])
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
|
|
const result = await getDashboardData()
|
|
|
|
expect(result.recentNotes).toEqual([])
|
|
expect(result.mostUsedNotes).toEqual([])
|
|
expect(result.recentCommands).toEqual([])
|
|
expect(result.recentSnippets).toEqual([])
|
|
expect(result.activityBasedNotes).toEqual([])
|
|
expect(result.hasActivity).toBe(false)
|
|
})
|
|
|
|
it('should convert dates to ISO strings', async () => {
|
|
;(prisma.note.findMany as jest.Mock).mockResolvedValue(mockNotes)
|
|
;(getRecentlyUsedNotes as jest.Mock).mockResolvedValue([])
|
|
|
|
const result = await getDashboardData(1)
|
|
|
|
expect(result.recentNotes[0].createdAt).toBe('2024-01-01T00:00:00.000Z')
|
|
expect(result.recentNotes[0].updatedAt).toBe('2024-01-15T00:00:00.000Z')
|
|
})
|
|
})
|
|
|
|
describe('hasVisibleBlocks', () => {
|
|
it('should return true when recentNotes has items', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [{ id: '1', title: 'Test', content: '', type: 'note' as const, isFavorite: false, isPinned: false, createdAt: '', updatedAt: '', tags: [] }],
|
|
mostUsedNotes: [],
|
|
recentCommands: [],
|
|
recentSnippets: [],
|
|
activityBasedNotes: [],
|
|
hasActivity: false,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(true)
|
|
})
|
|
|
|
it('should return true when mostUsedNotes has items', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [],
|
|
mostUsedNotes: [{ id: '1', title: 'Test', content: '', type: 'note' as const, isFavorite: false, isPinned: false, createdAt: '', updatedAt: '', tags: [], usageCount: 5 }],
|
|
recentCommands: [],
|
|
recentSnippets: [],
|
|
activityBasedNotes: [],
|
|
hasActivity: false,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(true)
|
|
})
|
|
|
|
it('should return true when recentCommands has items', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [],
|
|
mostUsedNotes: [],
|
|
recentCommands: [{ id: '1', title: 'Test', content: '', type: 'command' as const, isFavorite: false, isPinned: false, createdAt: '', updatedAt: '', tags: [] }],
|
|
recentSnippets: [],
|
|
activityBasedNotes: [],
|
|
hasActivity: false,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(true)
|
|
})
|
|
|
|
it('should return true when recentSnippets has items', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [],
|
|
mostUsedNotes: [],
|
|
recentCommands: [],
|
|
recentSnippets: [{ id: '1', title: 'Test', content: '', type: 'snippet' as const, isFavorite: false, isPinned: false, createdAt: '', updatedAt: '', tags: [] }],
|
|
activityBasedNotes: [],
|
|
hasActivity: false,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(true)
|
|
})
|
|
|
|
it('should return true when activityBasedNotes has items', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [],
|
|
mostUsedNotes: [],
|
|
recentCommands: [],
|
|
recentSnippets: [],
|
|
activityBasedNotes: [{ id: '1', title: 'Test', content: '', type: 'note' as const, isFavorite: false, isPinned: false, createdAt: '', updatedAt: '', tags: [] }],
|
|
hasActivity: true,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(true)
|
|
})
|
|
|
|
it('should return false when all arrays are empty', () => {
|
|
const data: DashboardData = {
|
|
recentNotes: [],
|
|
mostUsedNotes: [],
|
|
recentCommands: [],
|
|
recentSnippets: [],
|
|
activityBasedNotes: [],
|
|
hasActivity: false,
|
|
}
|
|
expect(hasVisibleBlocks(data)).toBe(false)
|
|
})
|
|
})
|
|
})
|