From c64c5d408c8c1c875eaed2a4435e4bda5c1c61c3 Mon Sep 17 00:00:00 2001 From: gilles Date: Sun, 13 Sep 2026 15:19:27 +0200 Subject: [PATCH] feat(notes): add note model and storage layer --- src/lib/notesStorage.test.ts | 60 ++++++++++++++++++++++++++++++++++++ src/lib/notesStorage.ts | 30 ++++++++++++++++++ src/types.ts | 5 +++ 3 files changed, 95 insertions(+) create mode 100644 src/lib/notesStorage.test.ts create mode 100644 src/lib/notesStorage.ts create mode 100644 src/types.ts diff --git a/src/lib/notesStorage.test.ts b/src/lib/notesStorage.test.ts new file mode 100644 index 0000000..037fa7f --- /dev/null +++ b/src/lib/notesStorage.test.ts @@ -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) + }) +}) diff --git a/src/lib/notesStorage.ts b/src/lib/notesStorage.ts new file mode 100644 index 0000000..214c477 --- /dev/null +++ b/src/lib/notesStorage.ts @@ -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 + return ( + typeof candidate.id === 'number' && + typeof candidate.title === 'string' && + typeof candidate.content === 'string' + ) +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ffb7c55 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,5 @@ +export interface Note { + id: number + title: string + content: string +}