feat(notes): add notes hook
This commit is contained in:
1 parent
c64c5d408c
commit
b86baee257
2 files changed
+136
No files matched your search
@@ -0,0 +1,103 @@
|
|||||||
|
import { act, renderHook } from '@testing-library/react'
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { useNotes } from './useNotes'
|
||||||
|
import { loadNotes } from '../lib/notesStorage'
|
||||||
|
import type { Note } from '../types'
|
||||||
|
|
||||||
|
describe('useNotes', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear()
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads existing notes from storage on mount', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'notes',
|
||||||
|
JSON.stringify([
|
||||||
|
{ id: 1, title: 'A', content: 'a' },
|
||||||
|
{ id: 2, title: 'B', content: 'b' },
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
expect(result.current.notes).toEqual([
|
||||||
|
{ id: 1, title: 'A', content: 'a' },
|
||||||
|
{ id: 2, title: 'B', content: 'b' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds a note and persists it', () => {
|
||||||
|
vi.spyOn(Date, 'now').mockReturnValue(1234)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.addNote()
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.notes).toEqual([{ id: 1234, title: '', content: '' }])
|
||||||
|
expect(loadNotes()).toEqual([{ id: 1234, title: '', content: '' }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates a note by id and persists it', () => {
|
||||||
|
const initial: Note[] = [{ id: 1, title: 'Old', content: 'old' }]
|
||||||
|
localStorage.setItem('notes', JSON.stringify(initial))
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateNote(1, { title: 'New', content: 'new' })
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.notes).toEqual([{ id: 1, title: 'New', content: 'new' }])
|
||||||
|
expect(loadNotes()[0]).toMatchObject({ id: 1, title: 'New', content: 'new' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ignores updates for unknown ids', () => {
|
||||||
|
const initial: Note[] = [{ id: 1, title: 'A', content: 'a' }]
|
||||||
|
localStorage.setItem('notes', JSON.stringify(initial))
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.updateNote(999, { title: 'X' })
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.notes).toEqual(initial)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deletes a note by id and persists it', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'notes',
|
||||||
|
JSON.stringify([
|
||||||
|
{ id: 1, title: 'A', content: 'a' },
|
||||||
|
{ id: 2, title: 'B', content: 'b' },
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.deleteNote(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.current.notes).toEqual([{ id: 2, title: 'B', content: 'b' }])
|
||||||
|
expect(loadNotes()).toEqual([{ id: 2, title: 'B', content: 'b' }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns notes sorted alphabetically, ignoring case', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'notes',
|
||||||
|
JSON.stringify([
|
||||||
|
{ id: 1, title: 'zebra', content: '' },
|
||||||
|
{ id: 2, title: 'Banane', content: '' },
|
||||||
|
{ id: 3, title: 'abricot', content: '' },
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useNotes())
|
||||||
|
|
||||||
|
expect(result.current.notes.map((n) => n.title)).toEqual(['abricot', 'Banane', 'zebra'])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import type { Note } from '../types'
|
||||||
|
import { loadNotes, saveNotes } from '../lib/notesStorage'
|
||||||
|
|
||||||
|
export function useNotes() {
|
||||||
|
const [notes, setNotes] = useState<Note[]>(() => loadNotes())
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
saveNotes(notes)
|
||||||
|
}, [notes])
|
||||||
|
|
||||||
|
const addNote = useCallback(() => {
|
||||||
|
const note: Note = { id: Date.now(), title: '', content: '' }
|
||||||
|
setNotes((current) => [...current, note])
|
||||||
|
return note
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const updateNote = useCallback((id: number, patch: Partial<Pick<Note, 'title' | 'content'>>) => {
|
||||||
|
setNotes((current) => current.map((note) => (note.id === id ? { ...note, ...patch } : note)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const deleteNote = useCallback((id: number) => {
|
||||||
|
setNotes((current) => current.filter((note) => note.id !== id))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const sortedNotes = useMemo(
|
||||||
|
() =>
|
||||||
|
[...notes].sort((a, b) => a.title.localeCompare(b.title, undefined, { sensitivity: 'base' })),
|
||||||
|
[notes],
|
||||||
|
)
|
||||||
|
|
||||||
|
return { notes: sortedNotes, addNote, updateNote, deleteNote }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user