diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ffab6..e8e36eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@behindthemusictree/genre-tree-view` `1.6.0`), which renders inside the library's own info panel. This replaces the standalone `GenreDetailPanel` component, which has been removed along with its export from `@behindthemusictree/app-kit`. +- **genre-tree**: `CriteriaDetailed` now includes `summary` (nullable string), matching the + backend's detail genre response, and `GenreTreeView`'s `renderExtraDetails` now displays it + above the essential tracks list when present. ### Fixed diff --git a/packages/app-kit/src/genre-tree/GenreTreeView.test.tsx b/packages/app-kit/src/genre-tree/GenreTreeView.test.tsx index f086c22..86bce48 100644 --- a/packages/app-kit/src/genre-tree/GenreTreeView.test.tsx +++ b/packages/app-kit/src/genre-tree/GenreTreeView.test.tsx @@ -723,6 +723,7 @@ describe("GenreTreeView", () => { data: { uuid: "c1", name: "Jazz", + summary: null, tracksCount: 5, tracksArchivedCount: 2, children: [], @@ -749,6 +750,39 @@ describe("GenreTreeView", () => { expect(screen.getByText(/2/)).toBeInTheDocument(); }); + it("renders the genre summary for the selected node", () => { + useListFullGenrePlaylistsMock.mockReturnValue({ + data: { results: [makePlaylist({ uuid: "gp1", criteria: { uuid: "c1", name: "Jazz" } })] }, + isPending: false, + }); + useFetchGenreDetailMock.mockImplementation((id: string | null) => + id === "c1" + ? { + data: { + uuid: "c1", + name: "Jazz", + summary: "Improvised music with swung rhythms.", + tracksCount: 5, + tracksArchivedCount: 0, + children: [], + essentialTracks: [], + }, + isPending: false, + } + : { data: undefined, isPending: false }, + ); + renderView(); + + selectGenre(); + + const output = treeWheelPropsMock.mock.calls.at(-1)?.[0].renderExtraDetails({ + id: "gp1", + }); + render(<>{output}); + + expect(screen.getByText("Improvised music with swung rhythms.")).toBeInTheDocument(); + }); + it("returns null when the node doesn't match the selected genre (e.g. info-panel chip navigation)", () => { useListFullGenrePlaylistsMock.mockReturnValue({ data: { @@ -765,6 +799,7 @@ describe("GenreTreeView", () => { data: { uuid: "c1", name: "Jazz", + summary: null, tracksCount: 5, tracksArchivedCount: 0, children: [], @@ -804,7 +839,7 @@ describe("GenreTreeView", () => { expect(output).toBeNull(); }); - it("returns null when there are no essential tracks and nothing archived", () => { + it("returns null when there is no summary, no essential tracks, and nothing archived", () => { useListFullGenrePlaylistsMock.mockReturnValue({ data: { results: [makePlaylist({ uuid: "gp1", criteria: { uuid: "c1", name: "Jazz" } })] }, isPending: false, @@ -815,6 +850,7 @@ describe("GenreTreeView", () => { data: { uuid: "c1", name: "Jazz", + summary: null, tracksCount: 5, tracksArchivedCount: 0, children: [], diff --git a/packages/app-kit/src/genre-tree/GenreTreeView.tsx b/packages/app-kit/src/genre-tree/GenreTreeView.tsx index 7ce87fb..2017aaf 100644 --- a/packages/app-kit/src/genre-tree/GenreTreeView.tsx +++ b/packages/app-kit/src/genre-tree/GenreTreeView.tsx @@ -107,13 +107,14 @@ export function GenreTreeView({ return null; } - const { essentialTracks, tracksArchivedCount } = selectedGenreDetail; - if (essentialTracks.length === 0 && tracksArchivedCount === 0) { + const { summary, essentialTracks, tracksArchivedCount } = selectedGenreDetail; + if (!summary && essentialTracks.length === 0 && tracksArchivedCount === 0) { return null; } return (
+ {summary &&

{summary}

} {tracksArchivedCount > 0 && (
Archived tracks: diff --git a/packages/app-kit/src/genre-tree/schemas/criteria/criteria.test.ts b/packages/app-kit/src/genre-tree/schemas/criteria/criteria.test.ts index 2ac5837..3d274a8 100644 --- a/packages/app-kit/src/genre-tree/schemas/criteria/criteria.test.ts +++ b/packages/app-kit/src/genre-tree/schemas/criteria/criteria.test.ts @@ -36,6 +36,7 @@ describe("CriteriaDetailedSchema", () => { const valid = { uuid, name: "Rock", + summary: "A genre summary", parent: null, ascendants: [], descendants: [], @@ -61,6 +62,15 @@ describe("CriteriaDetailedSchema", () => { it("rejects an invalid essentialTracks entry", () => { expect(() => CriteriaDetailedSchema.parse({ ...valid, essentialTracks: [{ uuid: "not-a-uuid" }] })).toThrow(); }); + + it("accepts a null summary", () => { + expect(() => CriteriaDetailedSchema.parse({ ...valid, summary: null })).not.toThrow(); + }); + + it("rejects a shape missing summary", () => { + const { summary: _summary, ...invalid } = valid; + expect(() => CriteriaDetailedSchema.parse(invalid)).toThrow(); + }); }); describe("CriteriaCreationSchema", () => { diff --git a/packages/app-kit/src/genre-tree/schemas/criteria/detailed.ts b/packages/app-kit/src/genre-tree/schemas/criteria/detailed.ts index 3be0ecd..394b6f5 100644 --- a/packages/app-kit/src/genre-tree/schemas/criteria/detailed.ts +++ b/packages/app-kit/src/genre-tree/schemas/criteria/detailed.ts @@ -9,6 +9,7 @@ import { CriteriaLineageRelWithoutDescendantSchema } from "./lineage-rel/without export const CriteriaDetailedSchema = UuidResourceSchema.extend({ name: z.string(), + summary: z.string().nullable(), parent: CriteriaMinimumSchema.nullable(), ascendants: z.array(CriteriaLineageRelWithoutDescendantSchema), descendants: z.array(CriteriaLineageRelWithoutAscendantSchema),