test: add tests for trash notes filtering and UI

- Added filterEntries tests: trash excluded from all/section/topic/archived,
  trash filter returns only trashed entries
- Added NoteList render tests: trash header, TRASHED badge, 30-day warning
  banner, empty trash state
- Updated all test files with trashed/trashedAt fields on VaultEntry mocks
- Updated useNoteActions to include trashed/trashedAt on new entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-02-21 16:56:48 +01:00
parent 2a01f44374
commit 8121bbf468
5 changed files with 170 additions and 3 deletions

View File

@@ -45,6 +45,8 @@ const mockEntry: VaultEntry = {
owner: 'Luca',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 1024,

View File

@@ -15,6 +15,8 @@ const mockEntry: VaultEntry = {
owner: 'Luca Rossi',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1707900000,
createdAt: null,
fileSize: 1024,
@@ -54,6 +56,8 @@ const referrerEntry: VaultEntry = {
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1707900000,
createdAt: null,
fileSize: 200,

View File

@@ -1,6 +1,6 @@
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { NoteList } from './NoteList'
import { NoteList, filterEntries } from './NoteList'
import type { VaultEntry, SidebarSelection } from '../types'
const allSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
@@ -19,6 +19,8 @@ const mockEntries: VaultEntry[] = [
owner: 'Luca',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 1024,
@@ -41,6 +43,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 847,
@@ -64,6 +68,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 320,
@@ -84,6 +90,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 512,
@@ -104,6 +112,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 256,
@@ -247,3 +257,129 @@ describe('NoteList', () => {
expect(screen.getByText('Build a personal knowledge management app.')).toBeInTheDocument()
})
})
// --- Trash feature tests ---
const trashedEntry: VaultEntry = {
path: '/vault/note/old-draft.md',
filename: 'old-draft.md',
title: 'Old Draft Notes',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
owner: null,
cadence: null,
archived: false,
trashed: true,
trashedAt: Date.now() / 1000 - 86400 * 5,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 280,
snippet: 'Some draft content that is no longer needed.',
relationships: {},
icon: null,
color: null,
}
const expiredTrashedEntry: VaultEntry = {
path: '/vault/note/deprecated-api.md',
filename: 'deprecated-api.md',
title: 'Deprecated API Notes',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
owner: null,
cadence: null,
archived: false,
trashed: true,
trashedAt: Date.now() / 1000 - 86400 * 35,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 190,
snippet: 'Old API docs replaced by v2.',
relationships: {},
icon: null,
color: null,
}
const entriesWithTrashed = [...mockEntries, trashedEntry, expiredTrashedEntry]
describe('filterEntries — trash', () => {
it('excludes trashed entries from "all" filter', () => {
const result = filterEntries(entriesWithTrashed, { kind: 'filter', filter: 'all' })
expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
expect(result.find((e) => e.title === 'Build Laputa App')).toBeDefined()
})
it('excludes trashed entries from section group', () => {
const result = filterEntries(entriesWithTrashed, { kind: 'sectionGroup', type: 'Note' })
expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
})
it('trash filter returns only trashed entries', () => {
const result = filterEntries(entriesWithTrashed, { kind: 'filter', filter: 'trash' })
expect(result).toHaveLength(2)
expect(result.every((e) => e.trashed)).toBe(true)
})
it('archived filter excludes trashed entries', () => {
const archivedAndTrashed = [
...mockEntries,
{ ...mockEntries[0], path: '/archived.md', archived: true, trashed: false, trashedAt: null, title: 'Archived Note' },
trashedEntry,
]
const result = filterEntries(archivedAndTrashed, { kind: 'filter', filter: 'archived' })
expect(result.find((e) => e.title === 'Archived Note')).toBeDefined()
expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
})
it('topic filter excludes trashed entries', () => {
const topicEntry: VaultEntry = { ...mockEntries[4] } // Software Development topic
const trashedWithTopic: VaultEntry = {
...trashedEntry,
relatedTo: ['[[topic/software-development]]'],
}
const all = [...mockEntries, trashedWithTopic]
const result = filterEntries(all, { kind: 'topic', entry: topicEntry })
expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
// Normal entry with that topic should still appear
expect(result.find((e) => e.title === 'Build Laputa App')).toBeDefined()
})
})
describe('NoteList — trash view', () => {
const trashSelection: SidebarSelection = { kind: 'filter', filter: 'trash' }
it('shows "Trash" header when trash filter is active', () => {
render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
expect(screen.getByText('Trash')).toBeInTheDocument()
})
it('shows only trashed entries in trash view', () => {
render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
expect(screen.getByText('Old Draft Notes')).toBeInTheDocument()
expect(screen.getByText('Deprecated API Notes')).toBeInTheDocument()
expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
})
it('shows TRASHED badge on trashed entries', () => {
render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
const badges = screen.getAllByText('TRASHED')
expect(badges.length).toBeGreaterThanOrEqual(1)
})
it('shows 30-day warning banner when expired notes exist', () => {
render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
expect(screen.getByText('Notes in trash for 30+ days will be permanently deleted')).toBeInTheDocument()
expect(screen.getByText(/1 note is past the 30-day retention period/)).toBeInTheDocument()
})
it('shows "Trash is empty" when no trashed entries', () => {
render(<NoteList entries={mockEntries} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
expect(screen.getByText('Trash is empty')).toBeInTheDocument()
})
})

View File

@@ -28,6 +28,8 @@ const mockEntries: VaultEntry[] = [
owner: 'Luca',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 1024,
@@ -48,6 +50,8 @@ const mockEntries: VaultEntry[] = [
owner: 'Luca',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 512,
@@ -68,6 +72,8 @@ const mockEntries: VaultEntry[] = [
owner: 'Luca',
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 256,
@@ -88,6 +94,8 @@ const mockEntries: VaultEntry[] = [
owner: 'Luca',
cadence: 'Weekly',
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 128,
@@ -108,6 +116,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 256,
@@ -128,6 +138,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 180,
@@ -148,6 +160,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 100,
@@ -168,6 +182,8 @@ const mockEntries: VaultEntry[] = [
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 200,
@@ -315,6 +331,8 @@ describe('Sidebar', () => {
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 200,
@@ -335,6 +353,8 @@ describe('Sidebar', () => {
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 200,
@@ -355,6 +375,8 @@ describe('Sidebar', () => {
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 300,
@@ -403,6 +425,8 @@ describe('Sidebar', () => {
owner: null,
cadence: null,
archived: false,
trashed: false,
trashedAt: null,
modifiedAt: 1700000000,
createdAt: null,
fileSize: 200,

View File

@@ -213,7 +213,8 @@ export function useNoteActions(
path, filename: `${slug}.md`, title, isA: type,
aliases: [], belongsTo: [], relatedTo: [],
status: noStatusTypes.has(type) ? null : 'Active',
owner: null, cadence: null, archived: false, modifiedAt: now, createdAt: now, fileSize: 0,
owner: null, cadence: null, archived: false, trashed: false, trashedAt: null,
modifiedAt: now, createdAt: now, fileSize: 0,
snippet: '', relationships: {}, icon: null, color: null,
}
@@ -240,7 +241,7 @@ export function useNoteActions(
const newEntry: VaultEntry = {
path, filename: `${slug}.md`, title: typeName, isA: 'Type',
aliases: [], belongsTo: [], relatedTo: [],
status: null, owner: null, cadence: null, archived: false,
status: null, owner: null, cadence: null, archived: false, trashed: false, trashedAt: null,
modifiedAt: now, createdAt: now, fileSize: 0,
snippet: '', relationships: {}, icon: null, color: null,
}