diff --git a/apps/readest-app/src/__tests__/utils/opds-feed.test.ts b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts index 7e7938fd..c28eb857 100644 --- a/apps/readest-app/src/__tests__/utils/opds-feed.test.ts +++ b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { getFeed, getPublication } from 'foliate-js/opds.js'; -import type { OPDSFeed, OPDSPublication } from '@/types/opds'; +import { SYMBOL, type OPDSFeed, type OPDSPublication } from '@/types/opds'; const MIME_XML = 'application/xml'; @@ -222,6 +222,33 @@ describe('OPDS feed parsing', () => { expect(pub.metadata.updated).toBe('2026-01-15T10:30:00.000Z'); }); + // Regression test for https://github.com/readest/readest/issues/4156 + // CWA (and other OPDS 1.x servers) place the book description in + // . foliate-js attaches it under SYMBOL.CONTENT, so the + // SYMBOL exported from @/types/opds must be the same Symbol instance the + // parser writes — otherwise PublicationView reads undefined and the + // description disappears from the book details page. + it('should expose via SYMBOL.CONTENT for OPDS 1.x feeds', () => { + const opds1Feed = ` + + CWA Library + + A Book With A Summary + A short blurb describing the book, set by the OPDS server in <summary>. + + +`; + const doc = parseXML(opds1Feed); + const feed = getFeed(doc) as OPDSFeed; + + expect(feed.publications).toHaveLength(1); + const metadata = feed.publications![0]!.metadata; + const content = metadata[SYMBOL.CONTENT]; + expect(content).toBeDefined(); + expect(content!.value).toContain('A short blurb describing the book'); + }); + it('should handle entries without id or updated gracefully', () => { const minimalFeed = ` diff --git a/apps/readest-app/src/types/opds.ts b/apps/readest-app/src/types/opds.ts index e57ebffa..c9e78312 100644 --- a/apps/readest-app/src/types/opds.ts +++ b/apps/readest-app/src/types/opds.ts @@ -1,3 +1,10 @@ +// SYMBOL must be re-exported from foliate-js so consumers read the same Symbol +// instances that the parser writes onto publication metadata. Declaring fresh +// `Symbol('content')` calls here would produce different identities, and +// `metadata[SYMBOL.CONTENT]` would silently return undefined — losing the book +// description for OPDS 1.x feeds where it lives in . +import { SYMBOL as FOLIATE_SYMBOL } from 'foliate-js/opds.js'; + export const REL = { ACQ: 'http://opds-spec.org/acquisition', FACET: 'http://opds-spec.org/facet', @@ -11,13 +18,7 @@ export const REL = { STREAM: 'http://vaemendis.net/opds-pse/stream', } as const; -const SUMMARY = Symbol('summary'); -const CONTENT = Symbol('content'); - -export const SYMBOL = { - SUMMARY, - CONTENT, -} as const; +export const SYMBOL = FOLIATE_SYMBOL as { SUMMARY: symbol; CONTENT: symbol }; export interface OPDSCatalog { id: string;