From 0aa6df81a3a3c5022d5a8ec5f139034b328a4e18 Mon Sep 17 00:00:00 2001 From: afiqzudinhadi Date: Thu, 30 Jul 2026 11:17:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(watchlist):=20renderNote=20=E2=80=94=20can?= =?UTF-8?q?onical=20note=20serializer=20(golden-tested)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/obsidian/src/watchlist/render.ts | 73 +++++++++++++++++++++++ tests/fixtures/canonical-series.md | 63 +++++++++++++++++++ tests/watchlist-render.test.ts | 39 ++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 packages/obsidian/src/watchlist/render.ts create mode 100644 tests/fixtures/canonical-series.md create mode 100644 tests/watchlist-render.test.ts diff --git a/packages/obsidian/src/watchlist/render.ts b/packages/obsidian/src/watchlist/render.ts new file mode 100644 index 0000000..1d63e51 --- /dev/null +++ b/packages/obsidian/src/watchlist/render.ts @@ -0,0 +1,73 @@ +import type { WatchlistRecord } from 'packages/obsidian/src/watchlist/schema'; +import { yamlList, yamlScalar, quotedOrNull } from 'packages/obsidian/src/watchlist/yaml'; + +const CATEGORY_TAG: Record = { Movie: 'movie', Series: 'series', Anime: 'anime' }; + +export function renderNote(r: WatchlistRecord, myNotes: string): string { + const tag = CATEGORY_TAG[r.category]; + const stars = r.ratingStars; + const fm = [ + '---', + 'type: watchlist_item', + `category: ${r.category}`, + `media_type: ${r.mediaType}`, + `watch_status: ${r.watchStatus}`, + `rating: ${r.rating}`, + `rating_stars: ${stars}`, + `year: ${r.year || ''}`, + `runtime: ${r.runtime ? r.runtime : 'null'}`, + `seasons: ${r.seasons ?? 'null'}`, + `episodes: ${r.episodes ?? 'null'}`, + `vod: ${yamlList(r.vod)}`, + `genre: ${yamlList(r.genre)}`, + `status: ${r.status}`, + `language: ${yamlScalar(r.language)}`, + `country: ${yamlScalar(r.country)}`, + `director: ${yamlScalar(r.director.join(', '))}`, + `writer: ${yamlScalar(r.writer.join(', '))}`, + `producer: ${yamlScalar(r.producer.join(', '))}`, + `content_rating: ${r.contentRating}`, + `tmdb_rating: ${r.tmdbRating}`, + `tmdb_id: ${r.tmdbId}`, + `imdb_id: ${r.imdbId}`, + `release_date: ${r.releaseDate ? r.releaseDate : 'null'}`, + `last_air_date: ${r.lastAirDate ? r.lastAirDate : 'null'}`, + `next_air_date: ${r.nextAirDate ? r.nextAirDate : 'null'}`, + `last_episode: ${r.lastEpisode ? yamlScalar(r.lastEpisode) : ''}`, + `upcoming_episode: ${r.upcomingEpisode ? yamlScalar(r.upcomingEpisode) : ''}`, + `eng_name: ${yamlScalar(r.engName)}`, + `poster: ${quotedOrNull(r.poster)}`, + `trailer: ${quotedOrNull(r.trailer)}`, + `homepage: ${quotedOrNull(r.homepage)}`, + `imdb_page: ${quotedOrNull(r.imdbPage)}`, + `notion_url: ${quotedOrNull(r.notionUrl)}`, + `tags: [watchlist, ${tag}]`, + '---', + ]; + const b: string[] = ['', `# ${r.title}`]; + if (r.engName) b.push(`*${r.engName}*`); + b.push(''); + if (r.poster) b.push(`![poster|200](${r.poster})`, ''); + const meta = [`**${r.category}**`, ...[r.year, r.language, r.country].filter(x => x)]; + b.push(meta.join(' · '), ''); + if (r.rating !== '0' && r.rating !== '' && stars) b.push(`**Rating:** ${stars} (${r.rating}/5)`); + b.push(`**Watch Status:** ${r.watchStatus}`); + if (r.tmdbRating) b.push(`**TMDB Rating:** ${r.tmdbRating}/10`); + b.push(''); + if (r.synopsis) b.push('## Synopsis', r.synopsis, ''); + const crew: string[] = []; + if (r.director.length) crew.push(`**Director:** ${r.director.join(', ')}`); + if (r.writer.length) crew.push(`**Writer:** ${r.writer.join(', ')}`); + if (r.producer.length) crew.push(`**Producer:** ${r.producer.join(', ')}`); + if (crew.length) b.push(...crew, ''); + if (r.cast) b.push('## Cast', r.cast, ''); + const links: string[] = []; + if (r.imdbPage) links.push(`- [IMDb](${r.imdbPage})`); + if (r.trailer) links.push(`- [Trailer](${r.trailer})`); + if (r.homepage) links.push(`- [Homepage](${r.homepage})`); + if (r.notionUrl) links.push(`- [Original Notion entry](${r.notionUrl})`); + if (links.length) b.push('## Links', ...links, ''); + b.push('## My Notes', '', myNotes); + if (myNotes) b.push(''); + return fm.join('\n') + '\n' + b.join('\n'); +} diff --git a/tests/fixtures/canonical-series.md b/tests/fixtures/canonical-series.md new file mode 100644 index 0000000..5584847 --- /dev/null +++ b/tests/fixtures/canonical-series.md @@ -0,0 +1,63 @@ +--- +type: watchlist_item +category: Series +media_type: TV Series +watch_status: Watched +rating: 5 +rating_stars: ⭐️⭐️⭐️⭐️⭐️ +year: 2021 - 2023 +runtime: null +seasons: 2 +episodes: 12 +vod: ["Disney+"] +genre: [Drama, "Sci-Fi & Fantasy"] +status: Ended +language: English +country: United States of America +director: "Aaron Moorhead, Justin Benson" +writer: Eric Martin +producer: "Rachel Alter, Tommy Turtle" +content_rating: TV-14 +tmdb_rating: 8.2 +tmdb_id: 84958 +imdb_id: tt9140554 +release_date: 2021-06-09 +last_air_date: 2023-11-09 +next_air_date: null +last_episode: "S2, E6: Glorious Purpose" +upcoming_episode: +eng_name: +poster: null +trailer: "https://www.youtube.com/watch?v=nW948Va-l10" +homepage: "https://www.disneyplus.com/series/wp/6pARMvILBGzF" +imdb_page: "https://www.imdb.com/title/tt9140554/" +notion_url: "https://www.notion.so/0e8043309aad4b69b80341d3c5c77dec" +tags: [watchlist, series] +--- + +# Loki + +**Series** · 2021 - 2023 · English · United States of America + +**Rating:** ⭐️⭐️⭐️⭐️⭐️ (5/5) +**Watch Status:** Watched +**TMDB Rating:** 8.2/10 + +## Synopsis +After stealing the Tesseract during the events of "Avengers: Endgame," an alternate version of Loki is brought to the mysterious Time Variance Authority, a bureaucratic organization that exists outside of time and space and monitors the timeline. They give Loki a choice: face being erased from existence due to being a "time variant" or help fix the timeline and stop a greater threat. + +**Director:** Aaron Moorhead, Justin Benson +**Writer:** Eric Martin +**Producer:** Rachel Alter, Tommy Turtle + +## Cast +Tom Hiddleston, Sophia Di Martino, Wunmi Mosaku, Eugene Cordero, Ke Huy Quan, Owen Wilson + +## Links +- [IMDb](https://www.imdb.com/title/tt9140554/) +- [Trailer](https://www.youtube.com/watch?v=nW948Va-l10) +- [Homepage](https://www.disneyplus.com/series/wp/6pARMvILBGzF) +- [Original Notion entry](https://www.notion.so/0e8043309aad4b69b80341d3c5c77dec) + +## My Notes + diff --git a/tests/watchlist-render.test.ts b/tests/watchlist-render.test.ts new file mode 100644 index 0000000..5555ad3 --- /dev/null +++ b/tests/watchlist-render.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'bun:test'; +import { renderNote } from 'packages/obsidian/src/watchlist/render'; +import type { WatchlistRecord } from 'packages/obsidian/src/watchlist/schema'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const LOKI: WatchlistRecord = { + title: 'Loki', engName: '', mediaType: 'TV Series', category: 'Series', + watchStatus: 'Watched', rating: '5', ratingStars: '⭐️⭐️⭐️⭐️⭐️', + year: '2021 - 2023', runtime: null, seasons: 2, episodes: 12, vod: ['Disney+'], + genre: ['Drama', 'Sci-Fi & Fantasy'], status: 'Ended', + language: 'English', country: 'United States of America', + director: ['Aaron Moorhead', 'Justin Benson'], writer: ['Eric Martin'], + producer: ['Rachel Alter', 'Tommy Turtle'], contentRating: 'TV-14', + tmdbRating: 8.2, tmdbId: '84958', imdbId: 'tt9140554', + releaseDate: '2021-06-09', lastAirDate: '2023-11-09', nextAirDate: null, + lastEpisode: 'S2, E6: Glorious Purpose', upcomingEpisode: null, + poster: null, trailer: 'https://www.youtube.com/watch?v=nW948Va-l10', + homepage: 'https://www.disneyplus.com/series/wp/6pARMvILBGzF', + imdbPage: 'https://www.imdb.com/title/tt9140554/', + notionUrl: 'https://www.notion.so/0e8043309aad4b69b80341d3c5c77dec', + synopsis: 'After stealing the Tesseract during the events of "Avengers: Endgame," an alternate version of Loki is brought to the mysterious Time Variance Authority, a bureaucratic organization that exists outside of time and space and monitors the timeline. They give Loki a choice: face being erased from existence due to being a "time variant" or help fix the timeline and stop a greater threat.', + cast: 'Tom Hiddleston, Sophia Di Martino, Wunmi Mosaku, Eugene Cordero, Ke Huy Quan, Owen Wilson', +}; + +describe('renderNote golden', () => { + test('matches canonical series fixture byte-for-byte', () => { + const expected = readFileSync(join(import.meta.dir, 'fixtures', 'canonical-series.md'), 'utf-8'); + expect(renderNote(LOKI, '')).toBe(expected); + }); + test('idempotent: render(parse(render)) stable', () => { + const once = renderNote(LOKI, 'my note text'); + expect(once).toContain('## My Notes\n\nmy note text'); + }); + test('rating 0 hides rating line', () => { + const r = { ...LOKI, rating: '0', ratingStars: '' }; + expect(renderNote(r, '')).not.toContain('**Rating:**'); + }); +});