Files
inkling/tests/unit/NoteCard.test.tsx
altair823 facbf54025 feat(v029): NoteRepository.setStatus + listByStatus + restoreNote 재구현
- NoteStatus 타입 추가 ('active'/'completed'/'archived'/'trashed')
- Note interface 에 status / statusChangedAt / moveReason 필드 추가
- setStatus(id, status, reason, now?) — 단일 transaction 으로 status + move_reason +
  status_changed_at + updated_at 갱신. status='trashed' ↔ deleted_at 동기화
  (backward compat). 그 외 status 는 deleted_at NULL.
- listByStatus(status, opts) — status 별 필터 + ORDER BY COALESCE(status_changed_at,
  created_at) DESC. limit cap 200.
- hydrate 에 status / statusChangedAt / moveReason 매핑 추가. 미설정 row 는 'active' fallback.
- restoreNote 재구현 — setStatus('active', null) 로 status + deleted_at 동기화 +
  v0.2.6 #10 round 1 fix (ai_status='failed'/'pending' → pending_jobs 재투입) 보존.
- 기존 테스트 fixture 5건에 새 필드 추가 (NoteCard, store.expired/recall/tagFilter/trash).
- 신규 테스트 11건 (setStatus + listByStatus + restoreNote 회귀).
2026-05-09 15:33:49 +09:00

85 lines
2.5 KiB
TypeScript

// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import type { Note } from '@shared/types';
const { mockOpenMedia } = vi.hoisted(() => ({
mockOpenMedia: vi.fn(async () => ({ ok: true }))
}));
vi.mock('../../src/renderer/inbox/api.js', () => ({
inboxApi: {
openMedia: mockOpenMedia,
deleteNote: vi.fn(),
restoreNote: vi.fn(),
permanentDeleteNote: vi.fn(),
updateAiFields: vi.fn(),
setDueDate: vi.fn(),
setIntent: vi.fn(),
dismissIntent: vi.fn()
}
}));
vi.mock('../../src/renderer/inbox/store.js', () => ({
useInbox: Object.assign(
() => ({}),
{ getState: () => ({ setTagFilter: vi.fn() }) }
)
}));
import { NoteCard } from '../../src/renderer/inbox/components/NoteCard';
const baseNote: Note = {
id: 'n1',
rawText: 'test',
aiTitle: 'T',
aiSummary: 'S',
aiStatus: 'done',
aiError: null,
aiProvider: null,
aiGeneratedAt: '2026-05-09T00:00:00Z',
titleEditedByUser: false,
summaryEditedByUser: false,
userIntent: null,
intentPromptedAt: '2026-05-09T00:00:00Z',
dueDate: null,
dueDateEditedByUser: false,
deletedAt: null,
lastRecalledAt: null,
recallDismissedAt: null,
status: 'active',
statusChangedAt: null,
moveReason: null,
createdAt: '2026-05-09T00:00:00Z',
updatedAt: '2026-05-09T00:00:00Z',
tags: [],
media: [
{ id: 'm1', kind: 'image', relPath: 'media/n1/img1.png', mime: 'image/png', bytes: 100 },
{ id: 'm2', kind: 'image', relPath: 'media/n1/img2.jpg', mime: 'image/jpeg', bytes: 200 }
]
};
describe('NoteCard — image rendering', () => {
beforeEach(() => {
vi.clearAllMocks();
cleanup();
});
it('renders <img> for each media item', () => {
render(<NoteCard note={baseNote} onUpdated={() => {}} mode="inbox" />);
const imgs = screen.getAllByRole('presentation');
expect(imgs).toHaveLength(2);
expect(imgs[0]?.getAttribute('src')).toBe('inkling-media://media/n1/img1.png');
expect(imgs[1]?.getAttribute('src')).toBe('inkling-media://media/n1/img2.jpg');
});
it('clicking <img> calls inboxApi.openMedia', () => {
render(<NoteCard note={baseNote} onUpdated={() => {}} mode="inbox" />);
const first = screen.getAllByRole('presentation')[0];
if (first === undefined) throw new Error('expected at least one img');
fireEvent.click(first);
expect(mockOpenMedia).toHaveBeenCalledWith('media/n1/img1.png');
});
});