feat(notes): add note model and storage layer
This commit is contained in:
1 parent
51584886b3
commit
c64c5d408c
3 files changed
+95
No files matched your search
@@ -0,0 +1,60 @@
|
|||||||
|
import { beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
import { loadNotes, saveNotes } from './notesStorage'
|
||||||
|
import type { Note } from '../types'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'notes'
|
||||||
|
|
||||||
|
describe('notesStorage', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads an empty array when nothing is stored', () => {
|
||||||
|
expect(loadNotes()).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('saves and loads notes', () => {
|
||||||
|
const notes: Note[] = [
|
||||||
|
{ id: 1, title: 'Courses', content: 'Lait, oeufs' },
|
||||||
|
{ id: 2, title: 'Pense-bête', content: 'Rappeler le plombier' },
|
||||||
|
]
|
||||||
|
|
||||||
|
saveNotes(notes)
|
||||||
|
|
||||||
|
expect(JSON.parse(localStorage.getItem(STORAGE_KEY)!)).toEqual(notes)
|
||||||
|
expect(loadNotes()).toEqual(notes)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an empty array when the stored JSON is invalid', () => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, 'not json {')
|
||||||
|
expect(loadNotes()).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an empty array when the stored value is not an array', () => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({ id: 1, title: 'x', content: 'y' }))
|
||||||
|
expect(loadNotes()).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters out entries that do not match the Note shape', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
STORAGE_KEY,
|
||||||
|
JSON.stringify([
|
||||||
|
{ id: 1, title: 'ok', content: 'ok' },
|
||||||
|
{ id: '2', title: 'bad id', content: 'x' },
|
||||||
|
{ title: 'missing id', content: 'x' },
|
||||||
|
'nope',
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(loadNotes()).toEqual([{ id: 1, title: 'ok', content: 'ok' }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('overwrites previously stored notes', () => {
|
||||||
|
saveNotes([{ id: 1, title: 'A', content: 'a' }])
|
||||||
|
const updated: Note[] = [{ id: 2, title: 'B', content: 'b' }]
|
||||||
|
|
||||||
|
saveNotes(updated)
|
||||||
|
|
||||||
|
expect(loadNotes()).toEqual(updated)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { Note } from '../types'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'notes'
|
||||||
|
|
||||||
|
export function loadNotes(): Note[] {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (raw === null) return []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed: unknown = JSON.parse(raw)
|
||||||
|
if (!Array.isArray(parsed)) return []
|
||||||
|
return parsed.filter(isNote)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveNotes(notes: Note[]): void {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNote(value: unknown): value is Note {
|
||||||
|
if (typeof value !== 'object' || value === null) return false
|
||||||
|
const candidate = value as Record<string, unknown>
|
||||||
|
return (
|
||||||
|
typeof candidate.id === 'number' &&
|
||||||
|
typeof candidate.title === 'string' &&
|
||||||
|
typeof candidate.content === 'string'
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Note {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
Reference in new issue
Block a user