From 68530c3150fe6ad0c6214d4b71f7c421401b93c1 Mon Sep 17 00:00:00 2001 From: minbros Date: Wed, 21 Oct 2026 11:57:00 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(map):=20=EC=9E=A5=EC=86=8C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20UI=EB=A5=BC=20=ED=94=BC=EA=B7=B8=EB=A7=88=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 검색 칩과 가로 스크롤 툴바, 검색 상태 및 장소 미리보기 정리 - 선택 핀 이름 우선순위와 경로 복귀 상태 유지 - 로그인 페이지 내부 헬퍼의 잘못된 export와 관련 테스트 수정 --- public/map-search/arrow-right.svg | 7 ++ public/map-search/attraction.svg | 8 ++ public/map-search/chevron-down.svg | 5 ++ public/map-search/clear.svg | 6 ++ public/map-search/close.svg | 6 ++ public/map-search/hotel.svg | 14 ++++ public/map-search/location.svg | 6 ++ public/map-search/museum.svg | 19 +++++ public/map-search/pointer.svg | 3 + public/map-search/reset.svg | 6 ++ public/map-search/restaurant.svg | 14 ++++ public/map-search/search-white.svg | 6 ++ public/map-search/search.svg | 6 ++ public/map-search/star.svg | 5 ++ public/map-search/transit.svg | 11 +++ src/app/globals.css | 27 ++++++- src/app/login/page.test.tsx | 11 ++- src/app/login/page.tsx | 2 +- src/components/filter-dropdown.tsx | 71 +++++++++++----- src/components/map/Map.search-rank.test.tsx | 29 ++++++- src/components/map/Map.tsx | 28 ++++--- src/components/map/MapCategoryChips.tsx | 22 ++--- .../map/MapDiscoverToolbar.test.tsx | 45 +++++++++++ src/components/map/MapDiscoverToolbar.tsx | 81 ++++++++----------- src/components/map/MapFilter.tsx | 11 +-- src/components/map/MapPinWithPlaceName.tsx | 8 +- src/components/map/MapPlaceSearch.test.tsx | 1 + src/components/map/MapPlaceSearch.tsx | 14 ++-- src/components/map/MapSearchIcon.tsx | 13 +++ src/components/map/MapWithDetailPanel.tsx | 5 +- src/components/map/SearchPlacePreview.tsx | 52 ++++++------ src/components/map/map-chip-button.ts | 29 ++----- src/components/map/map-place-categories.ts | 24 +++--- src/components/map/useMapPlaceSearch.test.tsx | 8 ++ src/components/map/useMapPlaceSearch.ts | 2 +- .../search/MapPlaceSearchInput.test.tsx | 27 ++++++- src/components/search/MapPlaceSearchInput.tsx | 56 +++++++++---- 37 files changed, 487 insertions(+), 201 deletions(-) create mode 100644 public/map-search/arrow-right.svg create mode 100644 public/map-search/attraction.svg create mode 100644 public/map-search/chevron-down.svg create mode 100644 public/map-search/clear.svg create mode 100644 public/map-search/close.svg create mode 100644 public/map-search/hotel.svg create mode 100644 public/map-search/location.svg create mode 100644 public/map-search/museum.svg create mode 100644 public/map-search/pointer.svg create mode 100644 public/map-search/reset.svg create mode 100644 public/map-search/restaurant.svg create mode 100644 public/map-search/search-white.svg create mode 100644 public/map-search/search.svg create mode 100644 public/map-search/star.svg create mode 100644 public/map-search/transit.svg create mode 100644 src/components/map/MapDiscoverToolbar.test.tsx create mode 100644 src/components/map/MapSearchIcon.tsx diff --git a/public/map-search/arrow-right.svg b/public/map-search/arrow-right.svg new file mode 100644 index 00000000..e03a7f79 --- /dev/null +++ b/public/map-search/arrow-right.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/map-search/attraction.svg b/public/map-search/attraction.svg new file mode 100644 index 00000000..30038055 --- /dev/null +++ b/public/map-search/attraction.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/map-search/chevron-down.svg b/public/map-search/chevron-down.svg new file mode 100644 index 00000000..688e5216 --- /dev/null +++ b/public/map-search/chevron-down.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/map-search/clear.svg b/public/map-search/clear.svg new file mode 100644 index 00000000..129f6879 --- /dev/null +++ b/public/map-search/clear.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/close.svg b/public/map-search/close.svg new file mode 100644 index 00000000..6712c055 --- /dev/null +++ b/public/map-search/close.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/hotel.svg b/public/map-search/hotel.svg new file mode 100644 index 00000000..e3fad22b --- /dev/null +++ b/public/map-search/hotel.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/map-search/location.svg b/public/map-search/location.svg new file mode 100644 index 00000000..c0bdec3e --- /dev/null +++ b/public/map-search/location.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/museum.svg b/public/map-search/museum.svg new file mode 100644 index 00000000..50eb214f --- /dev/null +++ b/public/map-search/museum.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/map-search/pointer.svg b/public/map-search/pointer.svg new file mode 100644 index 00000000..a6416b71 --- /dev/null +++ b/public/map-search/pointer.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/map-search/reset.svg b/public/map-search/reset.svg new file mode 100644 index 00000000..f0c45e54 --- /dev/null +++ b/public/map-search/reset.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/restaurant.svg b/public/map-search/restaurant.svg new file mode 100644 index 00000000..09b7a658 --- /dev/null +++ b/public/map-search/restaurant.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/map-search/search-white.svg b/public/map-search/search-white.svg new file mode 100644 index 00000000..97f48bc0 --- /dev/null +++ b/public/map-search/search-white.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/search.svg b/public/map-search/search.svg new file mode 100644 index 00000000..11b446ba --- /dev/null +++ b/public/map-search/search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map-search/star.svg b/public/map-search/star.svg new file mode 100644 index 00000000..357f0547 --- /dev/null +++ b/public/map-search/star.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/map-search/transit.svg b/public/map-search/transit.svg new file mode 100644 index 00000000..721fffd2 --- /dev/null +++ b/public/map-search/transit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/app/globals.css b/src/app/globals.css index 84be1375..ea5ef06f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -13,8 +13,11 @@ */ @custom-variant mobile (&:where(.is-mobile-device, .is-mobile-device *)); -/* 검색 말풍선은 Google 기본 여백·스크롤바 대신 본문에서 사방 12px을 확보한다. */ +/* 검색 말풍선의 여백·표면·그림자는 Figma 6156:14635 기준으로 맞춘다. */ .gm-style-iw-c:has([data-search-place-preview]) { + border-radius: 8px; + background: var(--color-fill-subtle); + box-shadow: 0 2px 10px #0000001a; padding: 0 !important; /* NOSONAR: S9423 — Google Maps가 주입하는 인라인 padding을 검색 말풍선에서만 재정의한다. */ } @@ -22,6 +25,28 @@ overflow: auto !important; /* NOSONAR: S9423 — Google Maps의 인라인 overflow:scroll에 의한 불필요한 스크롤바를 제거한다. */ } +/* 검색 추천은 기존 상세 패널(z-20) 위에서 선택할 수 있어야 한다. */ +.map-discover-toolbar:has(input[aria-expanded="true"]) { + z-index: 25; +} + +/* 원본 말풍선 꼬리(14×12)를 Google 기본 삼각형 대신 사용한다. */ +.gm-style-iw-t:has([data-search-place-preview]) > .gm-style-iw-tc { + width: 14px; + height: 12px; + filter: drop-shadow(0 2px 5px #0000001a); +} + +.gm-style-iw-t:has([data-search-place-preview]) > .gm-style-iw-tc::after { + inset: 0; + width: 14px; + height: 12px; + clip-path: none; + background: url("/map-search/pointer.svg") center / contain no-repeat; + transform: rotate(180deg); + box-shadow: none; +} + /* PC 지도 버튼은 지도 끝에서 아래·좌우 모두 24px — 확대·축소(+/−)의 아래는 저작권 줄과 기본 여백으로 이미 24px이라 오른쪽만 넓힌다. */ [data-map-controls-inset] .gm-bundled-control { margin-right: 24px !important; /* NOSONAR: S9423 — Google Maps가 주입하는 인라인 margin(10px)을 재정의한다. */ diff --git a/src/app/login/page.test.tsx b/src/app/login/page.test.tsx index d5df5b70..c5a407ef 100644 --- a/src/app/login/page.test.tsx +++ b/src/app/login/page.test.tsx @@ -16,11 +16,18 @@ vi.mock("@/lib/google-oauth", () => ({ saveOAuthPendingSession: vi.fn(), })); -import { LoginFallback } from "@/app/login/page"; +vi.mock("next/navigation", () => ({ + useSearchParams: () => { + // Exercise the page's real Suspense fallback while navigation data is pending. + throw new Promise(() => {}); + }, +})); + +import LoginPage from "@/app/login/page"; describe("LoginFallback", () => { it("renders meaningful non-interactive login HTML", () => { - const html = renderToStaticMarkup(); + const html = renderToStaticMarkup(); expect(html).toContain("로그인하고 여행 계획을 이어가세요"); expect(html).toContain("Google로 계속하기"); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 0e01c355..b165cc8d 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -108,7 +108,7 @@ function LoginPageContent() { ); } -export function LoginFallback() { +function LoginFallback() { return ( - {open && ( + {open && createPortal(
@@ -79,6 +113,7 @@ export function FilterDropdown({ onClick={() => { onChange(opt.value); setOpen(false); + triggerRef.current?.focus(); }} className={cn( "w-full cursor-pointer px-3 py-2 text-left text-label-m-regular mobile:text-label-s-regular transition hover:bg-gray-50", @@ -90,7 +125,7 @@ export function FilterDropdown({ {opt.label} ))} -
+ , document.body, )} ); diff --git a/src/components/map/Map.search-rank.test.tsx b/src/components/map/Map.search-rank.test.tsx index 8894a77b..985dad16 100644 --- a/src/components/map/Map.search-rank.test.tsx +++ b/src/components/map/Map.search-rank.test.tsx @@ -87,11 +87,13 @@ vi.mock("@vis.gl/react-google-maps", () => ({ AdvancedMarker: ({ children, onClick, + zIndex, }: { children: ReactNode; + zIndex?: number; onClick?: (event: { stop: () => void }) => void; }) => ( - ), @@ -231,18 +233,24 @@ it("web preview closes independently and switching pins never fetches or opens d await act(async () => pin("결과4").click()); expect(host.querySelector('[role="dialog"]')?.textContent).toContain("결과4"); }); -it("web specific autocomplete selection retains direct detail behavior when its pin is reopened", async () => { +it("web specific autocomplete opens preview, then detail only through its arrow, including reopen", async () => { await render(false); await act(async () => { await controller.confirm({ query: "원문", kind: "specific", generation: 0, session: { sessionToken: "token", expiresAt: Date.now() + 60000 }, prediction: prediction("result-4") }); }); + expect(views()).toHaveLength(0); + expect(host.querySelector('[data-search-place-preview]')?.textContent).toContain("결과4"); + await act(async () => host.querySelector('[aria-label="결과4 상세정보 보기"]')!.click()); expect(views()).toHaveLength(1); await act(async () => host.querySelector('[aria-label="닫기"]')!.click()); const marker = [...host.querySelectorAll("[data-marker]")].find(b => b.textContent === "결과4")!; await act(async () => marker.click()); - expect(host.querySelector('[aria-label="검색 장소 미리보기"]')).toBeNull(); + expect(host.querySelector('[data-search-place-preview]')).not.toBeNull(); + expect(views()).toHaveLength(1); + await act(async () => host.querySelector('[aria-label="결과4 상세정보 보기"]')!.click()); expect(views()).toHaveLength(2); expect(views().at(-1)![1].rank_bucket).toBeUndefined(); }); + it("web pins preview Text Search data without fetching detail; arrow opens detail with original rank", async () => { await render(false); boundary.page.mockResolvedValue({ items, nextPageToken: null }); @@ -474,3 +482,18 @@ it("discover ownership cancels pending search without showing its anchor", async ), ).toHaveLength(0); }); + +it("selected preview label receives marker priority and transfers it to the next selection", async () => { + await render(false); + boundary.page.mockResolvedValue({ items, nextPageToken: null }); + await act(async () => { await controller.confirm({ query: "카페", kind: "whole", session: null, generation: 0 }); }); + const markers = () => [...host.querySelectorAll("[data-marker]")]; + const first = () => markers().find(el => el.textContent === "결과1")!; + const second = () => markers().find(el => el.textContent === "결과2")!; + await act(async () => first().click()); + expect(Number(first().dataset.priority)).toBeGreaterThan(Number(second().dataset.priority || 0)); + expect(host.querySelector("[data-search-place-preview]")?.textContent).toContain("결과1"); + await act(async () => second().click()); + expect(Number(second().dataset.priority)).toBeGreaterThan(Number(first().dataset.priority || 0)); + expect(host.querySelector("[data-search-place-preview]")?.textContent).toContain("결과2"); +}); diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 2d8e7e5d..ffa600f8 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { usePathname } from "next/navigation"; import { Bookmark } from "lucide-react"; import { @@ -52,7 +52,7 @@ import { useRoomDetail } from "@/hooks/useRoomDetail"; import { useRoomsList } from "@/hooks/useRooms"; import { RouteIcon } from "@/assets/icons"; import { MapPinIcon } from "@/components/icons"; -import { MapPinWithPlaceName } from "@/components/map/MapPinWithPlaceName"; +import { MapPinWithPlaceName, MapPlaceName } from "@/components/map/MapPinWithPlaceName"; import { MapBookmarkPins } from "./MapBookmarkPins"; import { MAP_PIN_SELECTED_DISPLAY_SIZE_PX, @@ -329,11 +329,15 @@ function MapSearchResultPins() { const status = usePlaceSearchStore((s) => s.status); const kind = usePlaceSearchStore((s) => s.confirmationKind); const fittedItemsRef = useRef(null); - const preview = !isMobileDevice && kind === "whole" && !selectedPlace + const preview = pinsFocus === "search" && !isMobileDevice && !selectedPlace ? items.find((item) => item.googlePlaceId === searchSelectedId) : undefined; const selectedPlaceId = isMobileDevice && !selectedPlace ? searchSelectedId ?? "" : selectedPlace?.googlePlaceId?.trim() ?? ""; + useEffect(() => { + if (map && preview) panToWithinWorld(map, preview.location); + }, [map, preview]); + // 전체 검색 결과가 준비되면 첫 추천 대신 실제 결과들이 보이도록 맞춘다. useEffect(() => { if (!map || isMobileDevice || pinsFocus !== "search" || kind !== "whole" || status !== "done" || !items.length || usePlaceSearchStore.getState().selectedId || fittedItemsRef.current === items) return; @@ -356,17 +360,15 @@ function MapSearchResultPins() { .map((pin) => ( { e.stop(); const result = usePlaceSearchStore.getState().items.find((item) => item.googlePlaceId === pin.googlePlaceId); if (result) { usePlaceSearchStore.getState().select(result.googlePlaceId); - if (!isMobileDevice && kind === "specific") { - setSelectedPlace(result, { itinerarySource: "search", analyticsSource: "search", preserveMapZoom: true }); - } else { - setSelectedPlace(null); - } + setSelectedPlace(null); return; } releaseSearchForMapSelection(); @@ -387,7 +389,7 @@ function MapSearchResultPins() { ))} {preview ? ( { if (usePlaceSearchStore.getState().selectedId === preview.googlePlaceId) usePlaceSearchStore.getState().select(null); @@ -410,9 +412,11 @@ function MapSearchResultPins() { export default function TripMap({ routeView, onOpenRouteView, + search, }: { /** 있으면 경로 보기 — 검색·필터, 북마크·탐색 핀, 다른 일차 경로를 숨긴다 */ routeView?: MapRouteView; + search?: ReactNode; /** 있으면 지도 왼쪽 아래에 경로 보기 버튼을 둔다(모바일 지도) */ onOpenRouteView?: () => void; } = {}) { @@ -647,6 +651,7 @@ export default function TripMap({ {routeView ? null : ( )} ; diff --git a/src/components/map/MapCategoryChips.tsx b/src/components/map/MapCategoryChips.tsx index 0fbf4cc4..a55789ff 100644 --- a/src/components/map/MapCategoryChips.tsx +++ b/src/components/map/MapCategoryChips.tsx @@ -1,12 +1,11 @@ "use client"; -import { motion } from "framer-motion"; +import { MapSearchIcon } from "./MapSearchIcon"; import { cn } from "@/lib/utils"; import { MAP_PLACE_CATEGORIES } from "./map-place-categories"; import { mapChipButtonClassName } from "./map-chip-button"; -import { mapChipSpring } from "./map-toolbar-motion"; type MapCategoryChipsProps = { selectedCategoryId: string | null; @@ -28,35 +27,26 @@ export function MapCategoryChips({ return (
{MAP_PLACE_CATEGORIES.map((cat) => { - const Icon = cat.icon; const active = selectedCategoryId === cat.id; return ( - handleChipClick(cat.id)} - whileTap={{ scale: 0.94 }} - whileHover={{ scale: 1.02 }} - transition={mapChipSpring} + aria-pressed={active} className={cn( "flex items-center gap-1.5", mapChipButtonClassName(active), )} > - + {cat.label} - + ); })}
diff --git a/src/components/map/MapDiscoverToolbar.test.tsx b/src/components/map/MapDiscoverToolbar.test.tsx new file mode 100644 index 00000000..db0cba58 --- /dev/null +++ b/src/components/map/MapDiscoverToolbar.test.tsx @@ -0,0 +1,45 @@ +// @vitest-environment jsdom +import { act, useState } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, expect, it, vi } from "vitest"; +import { MapDiscoverToolbar } from "./MapDiscoverToolbar"; +import { MapToolbarLayoutProvider } from "@/contexts/MapToolbarLayoutContext"; +import type { OpenValue, RatingValue } from "./map-filters"; +vi.mock("@/contexts/MobileViewContext", () => ({ useMobileView: () => ({ isMobileDevice: false }) })); +let root: Root, host: HTMLDivElement; +afterEach(async () => { if (root) await act(async () => root.unmount()); host?.remove(); }); +it("keeps the selected category, applies rating/open filters, and reset clears all filters", async () => { + Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + function App() { + const [category, setCategory] = useState(null); + const [rating, setRating] = useState("all"); + const [open, setOpen] = useState("all"); + return {}}> + + {JSON.stringify({ category, rating, open })} + ; + } + host = document.createElement("div"); document.body.append(host); root = createRoot(host); + await act(async () => root.render()); + const button = (text: string) => [...document.querySelectorAll("button")].find(b => b.textContent === text)!; + await act(async () => button("음식점").click()); + expect(button("음식점").getAttribute("aria-pressed")).toBe("true"); + expect(host.textContent).not.toContain("호텔"); + await act(async () => button("영업 중").click()); + await act(async () => host.querySelector('[aria-label="평점 필터"]')!.click()); + const trigger = host.querySelector('[aria-label="평점 필터"]')!; + expect(document.getElementById(trigger.getAttribute("aria-controls")!)).not.toBeNull(); + expect(document.activeElement?.textContent).toBe("전체"); + // The menu is portalled out of the horizontal scroll clip, but its choices must still work. + const ratingChoice = [...document.querySelectorAll("button")].find(b => b.textContent?.includes("4.0"))!; + await act(async () => { + ratingChoice.dispatchEvent(new MouseEvent("mousedown", { bubbles: true })); + ratingChoice.click(); + }); + expect(JSON.parse(host.querySelector("output")!.textContent!)).toMatchObject({ category: "restaurant", open: "open" }); + expect(JSON.parse(host.querySelector("output")!.textContent!).rating).not.toBe("all"); + await act(async () => host.querySelector('[aria-label="카테고리 필터 초기화"]')!.click()); + expect(JSON.parse(host.querySelector("output")!.textContent!)).toEqual({ category: null, rating: "all", open: "all" }); + expect(button("호텔")).toBeDefined(); +}); diff --git a/src/components/map/MapDiscoverToolbar.tsx b/src/components/map/MapDiscoverToolbar.tsx index c5785c0b..74470590 100644 --- a/src/components/map/MapDiscoverToolbar.tsx +++ b/src/components/map/MapDiscoverToolbar.tsx @@ -1,8 +1,7 @@ "use client"; -import { useState } from "react"; +import { type ReactNode } from "react"; import { flushSync } from "react-dom"; -import { AnimatePresence, motion } from "framer-motion"; import { CircleCancelIcon, SearchIcon } from "@/assets/icons"; import { useMobileView } from "@/contexts/MobileViewContext"; @@ -10,20 +9,21 @@ import { useMobileMapSearchStore } from "@/stores/mobile-map-search-store"; import { useMapToolbarLayout } from "@/contexts/MapToolbarLayoutContext"; import { - MAP_TOOLBAR_ELEVATED_Z_CLASS, MAP_TOOLBAR_Z_CLASS, } from "@/lib/layout/mapDetailPanelLayout"; import { cn } from "@/lib/utils"; import MapFilter from "./MapFilter"; -import { MapCloseButton } from "./MapCloseButton"; +import { MapSearchIcon } from "./MapSearchIcon"; +import { MAP_PLACE_CATEGORIES } from "./map-place-categories"; +import { mapChipButtonClassName } from "./map-chip-button"; import { MapCategoryChips } from "./MapCategoryChips"; import type { OpenValue, RatingValue } from "./map-filters"; -import { mapToolbarPanelMotion } from "./map-toolbar-motion"; type MapDiscoverToolbarProps = { /** 웹 동선보기에서 숨김 — 상세 패널은 최솟값(필터 한 줄 아래) 자리로 간다 */ hidden?: boolean; + search?: ReactNode; selectedCategoryId: string | null; onSelectCategory: (id: string | null) => void; rating: RatingValue; @@ -34,6 +34,7 @@ type MapDiscoverToolbarProps = { export function MapDiscoverToolbar({ hidden = false, + search, selectedCategoryId, onSelectCategory, rating, @@ -43,54 +44,36 @@ export function MapDiscoverToolbar({ }: MapDiscoverToolbarProps) { const { setToolbarRef } = useMapToolbarLayout(); const { isMobileDevice } = useMobileView(); - const [ratingDropdownOpen, setRatingDropdownOpen] = useState(false); - - const [previousCategoryId, setPreviousCategoryId] = useState(selectedCategoryId); - if (previousCategoryId !== selectedCategoryId) { - setPreviousCategoryId(selectedCategoryId); - if (selectedCategoryId == null) setRatingDropdownOpen(false); + const category = MAP_PLACE_CATEGORIES.find((item) => item.id === selectedCategoryId); + function resetCategory() { + onSelectCategory(null); + setRating("all"); + setOpenNow("all"); } return ( - ); } diff --git a/src/components/map/MapPlaceSearch.test.tsx b/src/components/map/MapPlaceSearch.test.tsx index 554b203e..63a13fd2 100644 --- a/src/components/map/MapPlaceSearch.test.tsx +++ b/src/components/map/MapPlaceSearch.test.tsx @@ -66,6 +66,7 @@ it("specific Details failure retry returns to that input with a fresh session an host.remove(); }; await act(async () => root.render()); + await act(async () => (host.querySelector('button[aria-label="장소 검색 열기"]') as HTMLButtonElement).click()); const input = host.querySelector("input")!; await act(async () => { Object.getOwnPropertyDescriptor( diff --git a/src/components/map/MapPlaceSearch.tsx b/src/components/map/MapPlaceSearch.tsx index 9d9a64c0..22cb67e0 100644 --- a/src/components/map/MapPlaceSearch.tsx +++ b/src/components/map/MapPlaceSearch.tsx @@ -23,7 +23,6 @@ export function MapPlaceSearch({ mobile, hidden = false }: Readonly<{ const initialQuery = useSearchParams().get("q")?.trim() ?? ""; const status = usePlaceSearchStore((s) => s.status); const error = usePlaceSearchStore((s) => s.error); - const fallback = usePlaceSearchStore((s) => s.fallback); const items = usePlaceSearchStore((s) => s.items); const inputRef = useRef(null); const confirmationKind = usePlaceSearchStore((s) => s.confirmationKind); @@ -46,9 +45,9 @@ export function MapPlaceSearch({ mobile, hidden = false }: Readonly<{ void confirm({ query, kind: "whole", session: null, generation: 0 }); }; const hasFeedback = status === "pending" || status === "error" || - (status === "done" && (!items.length || fallback)); + (status === "done" && !items.length); const feedback = hasFeedback ? ( - 0} retry={retry} /> + 0} retry={retry} /> ) : null; return ( <> @@ -64,12 +63,13 @@ export function MapPlaceSearch({ mobile, hidden = false }: Readonly<{