fix(search): address request search review feedback

This commit is contained in:
Quick
2026-05-26 15:11:10 -04:00
parent 17a2cb0844
commit cd42610c95
9 changed files with 125 additions and 17 deletions
+5 -1
View File
@@ -240,7 +240,11 @@ describe("GlobalSearch + RequestToAddSection wiring", () => {
renderSearchMarkup({ defaultOpen: true, initialQuery: "Dune" });
const call = mocks.useRequestSearch.mock.calls[mocks.useRequestSearch.mock.calls.length - 1];
expect(call?.[3]).toEqual({ enabled: false });
expect(call?.[3]).toEqual({
enabled: false,
requireProfile: true,
staleTime: 5 * 60 * 1000,
});
});
it("does not mount RequestToAddSection when discovery is disabled", () => {
+2
View File
@@ -108,6 +108,8 @@ export function GlobalSearch({
const canRequest = useCanRequest();
const tmdbQuery = useRequestSearch("all", tmdbDebouncedQuery, 1, {
enabled: canRequest.discoveryEnabled,
requireProfile: true,
staleTime: 5 * 60 * 1000,
});
const tmdbMissingCount =
tmdbQuery.data?.results?.filter((result) => result.availability !== "available").length ?? 0;
@@ -95,7 +95,11 @@ describe("RequestToAddSection (dialog variant)", () => {
expect(call?.[0]).toBe("all");
expect(call?.[1]).toBe("dune");
expect(call?.[2]).toBe(1);
expect(call?.[3]).toEqual({ enabled: false });
expect(call?.[3]).toEqual({
enabled: false,
requireProfile: true,
staleTime: 5 * 60 * 1000,
});
});
it("passes enabled=true to useRequestSearch when discovery is enabled", () => {
@@ -113,7 +117,11 @@ describe("RequestToAddSection (dialog variant)", () => {
render(<RequestToAddSection variant="dialog" query="dune" libraryHadHits />);
const call = mocks.useRequestSearch.mock.calls[mocks.useRequestSearch.mock.calls.length - 1];
expect(call?.[3]).toEqual({ enabled: true });
expect(call?.[3]).toEqual({
enabled: true,
requireProfile: true,
staleTime: 5 * 60 * 1000,
});
});
it("renders 'Request to Add' header when library had hits", () => {
@@ -166,6 +174,16 @@ describe("RequestToAddSection (dialog variant)", () => {
expect(markup).toBe("");
});
it("keeps rendering cached TMDB results when a refetch errors", () => {
mocks.useRequestSearch.mockReturnValue({
data: { page: 1, total_pages: 1, total_results: 1, results: [missingResult()] },
isLoading: false,
isError: true,
});
const markup = render(<RequestToAddSection variant="dialog" query="dune" libraryHadHits />);
expect(markup).toContain("Dune: Prophecy");
});
it("renders nothing when all TMDB results are already in the library", () => {
mocks.useRequestSearch.mockReturnValue({
data: { page: 1, total_pages: 1, total_results: 1, results: [availableResult()] },
@@ -218,6 +236,32 @@ describe("RequestToAddSection (dialog variant)", () => {
expect(markup).toContain("Limit reached");
expect(markup).toContain('title="Limit reached"');
});
it("prefers request status over reason when a row is already requested", () => {
mocks.useRequestSearch.mockReturnValue({
data: {
page: 1,
total_pages: 1,
total_results: 1,
results: [
missingResult({
tmdb_id: 8,
title: "Already Pending Movie",
request: { requestable: false, reason: "blocked", status: "pending" },
}),
],
},
isLoading: false,
isError: false,
});
const markup = render(<RequestToAddSection variant="dialog" query="dune" libraryHadHits />);
expect(markup).toContain("Already Pending Movie");
expect(markup).toContain("Pending");
expect(markup).toContain('title="Pending"');
expect(markup).not.toContain('title="Blocked"');
});
});
describe("RequestToAddSection (grid variant)", () => {
+20 -9
View File
@@ -6,6 +6,7 @@ import { useCreateMediaRequest, useRequestSearch } from "@/hooks/queries/useRequ
import type { RequestMediaResult } from "@/api/types";
import {
formatRequestReason,
formatRequestStatus,
requestInputFromMediaResult,
tmdbImageURL,
} from "@/lib/mediaRequests";
@@ -16,6 +17,16 @@ function cardKey(item: Pick<RequestMediaResult, "media_type" | "tmdb_id">): stri
return `${item.media_type}-${item.tmdb_id}`;
}
function nonRequestableLabel(item: RequestMediaResult): string {
if (item.request.status) {
return formatRequestStatus(item.request.status);
}
if (item.request.reason) {
return formatRequestReason(item.request.reason);
}
return "Blocked";
}
const DIALOG_LIMIT = 4;
const GRID_LIMIT = 20;
@@ -28,10 +39,14 @@ export type RequestToAddSectionProps = {
export function RequestToAddSection({ variant, query, libraryHadHits }: RequestToAddSectionProps) {
const { discoveryEnabled } = useCanRequest();
const search = useRequestSearch("all", query, 1, { enabled: discoveryEnabled });
const search = useRequestSearch("all", query, 1, {
enabled: discoveryEnabled,
requireProfile: true,
staleTime: 5 * 60 * 1000,
});
if (!discoveryEnabled) return null;
if (search.isError) return null;
if (search.isError && !search.data) return null;
const filtered = (search.data?.results ?? []).filter((item) => item.availability !== "available");
if (filtered.length === 0) return null;
@@ -89,11 +104,7 @@ function DialogRow({ item }: { item: RequestMediaResult }) {
const poster = tmdbImageURL(item.poster_path);
const Icon = item.media_type === "series" ? Tv : Film;
const requestable = item.request.requestable;
const reasonLabel = !requestable
? item.request.reason
? formatRequestReason(item.request.reason)
: "Blocked"
: null;
const unavailableLabel = requestable ? null : nonRequestableLabel(item);
return (
<Link
@@ -128,9 +139,9 @@ function DialogRow({ item }: { item: RequestMediaResult }) {
) : (
<span
className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-[9px] font-medium tracking-[0.5px] text-white/60 uppercase"
title={reasonLabel ?? "Blocked"}
title={unavailableLabel ?? "Blocked"}
>
{reasonLabel}
{unavailableLabel}
</span>
)}
</Link>