test: add unit tests for hooks (useBookmarks, useUpdateCheck)
- Export loadBookmarks/saveBookmarks and isNewer for testability - Add 12 tests covering bookmark persistence and semver comparison - Total: 165 tests passing
This commit is contained in:
56
src/hooks/__tests__/useBookmarks.test.ts
Normal file
56
src/hooks/__tests__/useBookmarks.test.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { loadBookmarks, saveBookmarks, type Bookmark } from '../useBookmarks';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'pinchchat-bookmarks';
|
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const store: Record<string, string> = {};
|
||||||
|
const localStorageMock = {
|
||||||
|
getItem: vi.fn((key: string) => store[key] ?? null),
|
||||||
|
setItem: vi.fn((key: string, value: string) => { store[key] = value; }),
|
||||||
|
removeItem: vi.fn((key: string) => { delete store[key]; }),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
for (const key of Object.keys(store)) delete store[key];
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadBookmarks', () => {
|
||||||
|
it('returns empty array when nothing stored', () => {
|
||||||
|
expect(loadBookmarks()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns parsed bookmarks from localStorage', () => {
|
||||||
|
const bookmarks: Bookmark[] = [
|
||||||
|
{ messageId: 'msg1', sessionKey: 'sess1', preview: 'Hello', timestamp: 1000, bookmarkedAt: 2000 },
|
||||||
|
];
|
||||||
|
store[STORAGE_KEY] = JSON.stringify(bookmarks);
|
||||||
|
expect(loadBookmarks()).toEqual(bookmarks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array on corrupt JSON', () => {
|
||||||
|
store[STORAGE_KEY] = '{broken';
|
||||||
|
expect(loadBookmarks()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('saveBookmarks', () => {
|
||||||
|
it('persists bookmarks to localStorage', () => {
|
||||||
|
const bookmarks: Bookmark[] = [
|
||||||
|
{ messageId: 'msg1', sessionKey: 'sess1', preview: 'Test', timestamp: 1000, bookmarkedAt: 2000 },
|
||||||
|
];
|
||||||
|
saveBookmarks(bookmarks);
|
||||||
|
expect(JSON.parse(store[STORAGE_KEY]!)).toEqual(bookmarks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overwrites existing bookmarks', () => {
|
||||||
|
saveBookmarks([{ messageId: 'old', sessionKey: 's', preview: '', timestamp: 0, bookmarkedAt: 0 }]);
|
||||||
|
saveBookmarks([{ messageId: 'new', sessionKey: 's', preview: '', timestamp: 0, bookmarkedAt: 0 }]);
|
||||||
|
const stored = JSON.parse(store[STORAGE_KEY]!);
|
||||||
|
expect(stored).toHaveLength(1);
|
||||||
|
expect(stored[0].messageId).toBe('new');
|
||||||
|
});
|
||||||
|
});
|
||||||
33
src/hooks/__tests__/useUpdateCheck.test.ts
Normal file
33
src/hooks/__tests__/useUpdateCheck.test.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { isNewer } from '../useUpdateCheck';
|
||||||
|
|
||||||
|
describe('isNewer', () => {
|
||||||
|
it('returns true when remote major is higher', () => {
|
||||||
|
expect(isNewer('2.0.0', '1.0.0')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when remote minor is higher', () => {
|
||||||
|
expect(isNewer('1.5.0', '1.4.0')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when remote patch is higher', () => {
|
||||||
|
expect(isNewer('1.4.2', '1.4.1')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when versions are equal', () => {
|
||||||
|
expect(isNewer('1.4.1', '1.4.1')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when remote is older', () => {
|
||||||
|
expect(isNewer('1.3.9', '1.4.0')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles missing patch segments', () => {
|
||||||
|
expect(isNewer('1.1', '1.0.9')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles large version numbers', () => {
|
||||||
|
expect(isNewer('1.66.1', '1.66.0')).toBe(true);
|
||||||
|
expect(isNewer('1.66.0', '1.66.1')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,7 +10,7 @@ export interface Bookmark {
|
|||||||
bookmarkedAt: number;
|
bookmarkedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadBookmarks(): Bookmark[] {
|
export function loadBookmarks(): Bookmark[] {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY);
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
if (raw) return JSON.parse(raw) as Bookmark[];
|
if (raw) return JSON.parse(raw) as Bookmark[];
|
||||||
@@ -18,7 +18,7 @@ function loadBookmarks(): Bookmark[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveBookmarks(bookmarks: Bookmark[]) {
|
export function saveBookmarks(bookmarks: Bookmark[]) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks));
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(bookmarks));
|
||||||
} catch { /* noop */ }
|
} catch { /* noop */ }
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function useUpdateCheck(currentVersion: string): UpdateInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** True if remote is newer than local (semver compare) */
|
/** True if remote is newer than local (semver compare) */
|
||||||
function isNewer(remote: string, local: string): boolean {
|
export function isNewer(remote: string, local: string): boolean {
|
||||||
const r = remote.split('.').map(Number);
|
const r = remote.split('.').map(Number);
|
||||||
const l = local.split('.').map(Number);
|
const l = local.split('.').map(Number);
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user