From f6bea623bf207d0f3893e903c30be1cf2fe95066 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 9 May 2026 14:06:21 +0900 Subject: [PATCH] =?UTF-8?q?feat(v028):=20NoteCard=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=20=EB=A0=8C=EB=8D=94=EB=A7=81=20+=20onClick?= =?UTF-8?q?=20(openMedia=20=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=EB=8A=94?= =?UTF-8?q?=20Task=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 회색 placeholder div → 로 교체 - onClick 으로 inboxApi.openMedia(relPath) 호출 (현재는 InboxApi 인터페이스에 부재 → unknown cast 사용; Task 3 에서 정식 시그니처 추가 후 cast 제거 예정) - alt='' 로 decorative 처리 (role=presentation), title 에 relPath 유지 - flex-wrap 추가 — 다수 이미지 시 줄바꿈 Tests: tests/unit/NoteCard.test.tsx 신규 2건 (img src 검증, click → openMedia 호출) 회귀: 468 → 470 pass --- src/renderer/inbox/components/NoteCard.tsx | 18 ++++- tests/unit/NoteCard.test.tsx | 81 ++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/unit/NoteCard.test.tsx diff --git a/src/renderer/inbox/components/NoteCard.tsx b/src/renderer/inbox/components/NoteCard.tsx index b9242e2..5a56721 100644 --- a/src/renderer/inbox/components/NoteCard.tsx +++ b/src/renderer/inbox/components/NoteCard.tsx @@ -332,9 +332,23 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore )} {local.media.length > 0 && ( -
+
{local.media.map((m) => ( -
+ { void (inboxApi as unknown as { openMedia: (rel: string) => Promise }).openMedia(m.relPath); }} + style={{ + width: 48, + height: 48, + objectFit: 'cover', + borderRadius: 4, + cursor: 'pointer', + border: '1px solid #e0e0e0' + }} + /> ))}
)} diff --git a/tests/unit/NoteCard.test.tsx b/tests/unit/NoteCard.test.tsx new file mode 100644 index 0000000..cff2f66 --- /dev/null +++ b/tests/unit/NoteCard.test.tsx @@ -0,0 +1,81 @@ +// @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, + 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 for each media item', () => { + render( {}} 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 calls inboxApi.openMedia', () => { + render( {}} 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'); + }); +});