From 343ce531caa7caf697f57c2a006ce1ecd96c94e3 Mon Sep 17 00:00:00 2001 From: jdluu Date: Fri, 28 Aug 2026 23:53:20 -0700 Subject: [PATCH] refactor: centralize acquisition format helpers Share media-type normalization and display labels across OPDS cards, modals, and format menus while preserving download behavior.\n\nCloses #98 --- src/__tests__/types/opds.test.ts | 46 ++++++++++++++++++++ src/features/opds/OpdsPublicationCard.tsx | 29 +++--------- src/features/opds/PublicationDetailModal.tsx | 15 +++---- src/features/opds/PublicationFormatMenu.tsx | 7 +-- src/types/opds.ts | 32 ++++++++++++++ 5 files changed, 93 insertions(+), 36 deletions(-) diff --git a/src/__tests__/types/opds.test.ts b/src/__tests__/types/opds.test.ts index 31b3af2..4b89f38 100644 --- a/src/__tests__/types/opds.test.ts +++ b/src/__tests__/types/opds.test.ts @@ -3,7 +3,9 @@ import type { Acquisition, Publication } from "@/types/opds"; import { findAcquisitionLinkForFormat, getAcquisitionLinks, + getAcquisitionMediaType, getDownloadableFormats, + getMediaTypeDisplayLabel, hasDownloadableFormats, isAcquisitionLink, } from "@/types/opds"; @@ -112,3 +114,47 @@ describe("findAcquisitionLinkForFormat", () => { expect(findAcquisitionLinkForFormat(htmlOnly, "application/epub+zip")).toBeNull(); }); }); + +describe("getMediaTypeDisplayLabel", () => { + it("maps EPUB and PDF to short labels", () => { + expect(getMediaTypeDisplayLabel("application/epub+zip")).toBe("EPUB"); + expect(getMediaTypeDisplayLabel("application/pdf")).toBe("PDF"); + }); + + it("maps the richer badge set for known non-EPUB/PDF types", () => { + expect(getMediaTypeDisplayLabel("application/pdf+aes")).toBe("PDF (Encrypted)"); + expect(getMediaTypeDisplayLabel("application/zip")).toBe("ZIP"); + expect(getMediaTypeDisplayLabel("application/x-mobipocket-ebook")).toBe("MOBI"); + expect(getMediaTypeDisplayLabel("image/jpeg")).toBe("JPEG"); + }); + + it("matches case-insensitively", () => { + expect(getMediaTypeDisplayLabel("Application/EPUB+ZIP")).toBe("EPUB"); + }); + + it("falls back to the raw media type for unknown formats", () => { + expect(getMediaTypeDisplayLabel("application/x-custom-format")).toBe( + "application/x-custom-format", + ); + }); +}); + +describe("getAcquisitionMediaType", () => { + it("prefers media_type over type", () => { + expect( + getAcquisitionMediaType( + link({ href: "/x", media_type: "application/pdf", type: "application/epub+zip" }), + ), + ).toBe("application/pdf"); + }); + + it("falls back to the type when media_type is absent", () => { + expect(getAcquisitionMediaType(link({ href: "/x", type: "application/epub+zip" }))).toBe( + "application/epub+zip", + ); + }); + + it("defaults to EPUB when neither media_type nor type is present", () => { + expect(getAcquisitionMediaType(link({ href: "/x" }))).toBe("application/epub+zip"); + }); +}); diff --git a/src/features/opds/OpdsPublicationCard.tsx b/src/features/opds/OpdsPublicationCard.tsx index 5f805de..6dae719 100644 --- a/src/features/opds/OpdsPublicationCard.tsx +++ b/src/features/opds/OpdsPublicationCard.tsx @@ -10,8 +10,9 @@ import type React from "react"; import { Button } from "@/components/ui/Button"; import type { CategorizedLibraryRecord, PublicationLibraryInfo } from "@/types/offline"; import type { DownloadStatus, MediaType, Publication } from "@/types/opds"; +import { getMediaTypeDisplayLabel } from "@/types/opds"; import { LibraryStateBadge } from "./LibraryStateBadge"; -import { getMediaTypeLabel, PublicationFormatMenu } from "./PublicationFormatMenu"; +import { PublicationFormatMenu } from "./PublicationFormatMenu"; import { usePublicationState } from "./usePublicationState"; interface OpdsPublicationCardProps { @@ -41,24 +42,6 @@ interface OpdsPublicationCardProps { onViewDetails?: (publication: Publication) => void; } -function getMediaTypeLabelForBadge(mediaType: string): string { - const labels: Record = { - "application/epub+zip": "EPUB", - "application/pdf": "PDF", - "application/pdf+aes": "PDF (Encrypted)", - "application/zip": "ZIP", - "chemical/x-mdldrum": "MDL", - "chemical/x-mol": "MOL", - "text/html": "HTML", - "application/rtf": "RTF", - "application/x-mobipocket-ebook": "MOBI", - "application/x-kindle": "Kindle", - "image/jpeg": "JPEG", - "image/png": "PNG", - }; - return labels[mediaType.toLowerCase()] || mediaType; -} - export const OpdsPublicationCard: React.FC = ({ publication, showFormats = true, @@ -192,7 +175,7 @@ export const OpdsPublicationCard: React.FC = ({ {formatLabels.map((fmt) => (
  • - {getMediaTypeLabelForBadge(fmt)} + {getMediaTypeDisplayLabel(fmt)}
  • ))} @@ -260,7 +243,7 @@ export const OpdsPublicationCard: React.FC = ({ size="sm" onClick={handleDownload} className="w-full outline-none focus-visible:ring-2 focus-visible:ring-primary" - aria-label={`Download ${publication.title} as ${getMediaTypeLabel(selectedFormat)}`} + aria-label={`Download ${publication.title} as ${getMediaTypeDisplayLabel(selectedFormat)}`} >