From f87cf3bd69f69aa7072d3f6452fc0be9e33a573a Mon Sep 17 00:00:00 2001 From: mokjakA Date: Wed, 12 Aug 2026 10:12:25 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(S15P11A705-437):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=98=81=EC=97=AD=20?= =?UTF-8?q?=ED=99=95=EB=8C=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PlaceRecordSheet.test.tsx | 4 +- .../records/components/PlaceRecordSheet.tsx | 41 ++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/features/records/components/PlaceRecordSheet.test.tsx b/src/features/records/components/PlaceRecordSheet.test.tsx index 542e5f8..1682a82 100644 --- a/src/features/records/components/PlaceRecordSheet.test.tsx +++ b/src/features/records/components/PlaceRecordSheet.test.tsx @@ -147,7 +147,9 @@ describe('PlaceRecordSheet 760px 노트 밀도', () => { (button) => button.textContent?.trim() === '이 이미지로 분석 시작', ); - expect(dropzone?.className).toContain('min-h-[120px]'); + expect(dropzone?.className).toContain('flex-1'); + expect(dropzone?.closest('div')?.className).toContain('min-h-[156px]'); + expect(dropzone?.closest('div')?.className).toContain('my-3'); expect(analyzeButton?.className).toContain('h-12'); expect(analyzeButton?.className).not.toContain('h-[60px]'); }); diff --git a/src/features/records/components/PlaceRecordSheet.tsx b/src/features/records/components/PlaceRecordSheet.tsx index f7a857e..643101c 100644 --- a/src/features/records/components/PlaceRecordSheet.tsx +++ b/src/features/records/components/PlaceRecordSheet.tsx @@ -683,7 +683,7 @@ export function PlaceRecordSheet({ previewMode = false, onRecordSaved }: PlaceRe * 분석된 이미지는 저장되지 않으며, 장소 후보 확인에만 사용됩니다.

-
+
From 6fdb97fc0d078afe6282b958d67607718734ebb4 Mon Sep 17 00:00:00 2001 From: mokjakA Date: Wed, 12 Aug 2026 10:12:52 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(S15P11A705-437):=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EB=84=88?= =?UTF-8?q?=EB=B9=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/SearchResultGallery.test.tsx | 66 +++++++++++++++++ .../home/components/SearchResultGallery.tsx | 14 ++-- src/features/home/paperAperture.css | 70 +++++++++++++++++-- 3 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 src/features/home/components/SearchResultGallery.test.tsx diff --git a/src/features/home/components/SearchResultGallery.test.tsx b/src/features/home/components/SearchResultGallery.test.tsx new file mode 100644 index 0000000..5d01421 --- /dev/null +++ b/src/features/home/components/SearchResultGallery.test.tsx @@ -0,0 +1,66 @@ +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { SearchResultItem } from '@/features/search/api/searchRecords'; +import { SearchResultGallery } from './SearchResultGallery'; + +const ITEMS: SearchResultItem[] = Array.from({ length: 4 }, (_, index) => ({ + recordId: index + 1, + similarity: 1 - index * 0.1, + place: { + placeId: index + 10, + name: `장소 ${index + 1}`, + address: `주소 ${index + 1}`, + lat: 37.5, + lng: 127, + }, + matchedContext: { + contextId: index + 20, + body: `맥락 ${index + 1}`, + createdAt: '2026-08-12T00:00:00+09:00', + }, + keywords: [], + keywordStatus: 'COMPLETED', + createdAt: '2026-08-12T00:00:00+09:00', +})); + +let container: HTMLDivElement; +let root: Root; + +beforeEach(() => { + (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); +}); + +describe('SearchResultGallery', () => { + it('가로 탐색 가능한 결과 영역과 응답 순서의 카드를 렌더한다', () => { + const onSelectRecord = vi.fn(); + + act(() => { + root.render(); + }); + + const gallery = container.querySelector('.pl-results__gallery'); + const cards = Array.from(container.querySelectorAll('.pl-results__card')); + + expect(gallery?.getAttribute('role')).toBe('region'); + expect(gallery?.getAttribute('aria-label')).toBe('검색 결과 4곳'); + expect(gallery?.tabIndex).toBe(0); + expect(cards.map((card) => card.textContent)).toEqual([ + expect.stringContaining('장소 1/4'), + expect.stringContaining('장소 2/4'), + expect.stringContaining('장소 3/4'), + expect.stringContaining('장소 4/4'), + ]); + + act(() => cards[2].click()); + expect(onSelectRecord).toHaveBeenCalledWith(3); + }); +}); diff --git a/src/features/home/components/SearchResultGallery.tsx b/src/features/home/components/SearchResultGallery.tsx index 9646a52..86fa9e1 100644 --- a/src/features/home/components/SearchResultGallery.tsx +++ b/src/features/home/components/SearchResultGallery.tsx @@ -26,21 +26,27 @@ interface SearchResultGalleryProps { * `POST /search/records`는 "내 기록의 맥락"만 검색하므로(api-contract.md "Place · 지도 · 검색" * 표) matchedContext.body는 항상 본인 Context 원문이라 공개 범위 규칙(타인 Context 원문 비공개) * 위반이 아니다. attachment='flat'을 쓴 이유는 lifted보다 회전·그림자가 절제돼 있어 좁은 - * 카드(w-72) 안에서 다른 항목(이름·위치·키워드)과 부딪히지 않기 때문 — 디자인 시안이 도착하면 - * 이 선택은 다시 확인한다. + * 카드 안에서 다른 항목(이름·위치·키워드)과 부딪히지 않기 때문 — 디자인 시안이 도착하면 + * 이 선택은 다시 확인한다. S15P11A705-437부터 카드 폭은 고정값이 아니라 갤러리 가용 폭의 + * 3등분이다. 바깥 결과 영역이 좁아져도 세 번째 카드만 잘리는 대신 세 카드가 함께 줄어든다. * editable을 넘기지 않는다(기본 false) — 검색 결과 카드는 Context를 고치는 자리가 아니다. */ export function SearchResultGallery({ items, onSelectRecord }: SearchResultGalleryProps) { const total = items.length; return ( -
+
{items.map((item, index) => (