/** * PersonalReader progress on Library rows and synced stock on Store options. * Synced values are the shapes the content service's jobs store (see * services/content/src/sync/jobs/); a note's own values always win, and a * note with nothing synced renders as before. */ import { describe, expect, it } from "vitest"; import { withSynced, type Note } from "../src/lib/content.ts"; import { itemDetails, readerProgress, type Item } from "../src/lib/library.ts"; import { optionGroups, type Line } from "../src/lib/store.ts"; const note = (type: string, data: Record, synced?: Record) => withSynced({ path: `${type}/x.md`, type, name: "x", slug: "x", title: "X", body: "", acceptedAt: "", data: { type, ...data }, synced } as unknown as T); describe("Library progress from PersonalReader", () => { const base = { medium: "manhwa", state: "now", creator: "Someone", year: 2020 }; it("adds the chapter line to the details", () => { const i = note("library-item", base, { chapterProgress: { title: "X", status: "now", chapter: 142, readAt: "2026-10-03T05:00:00.000Z" } }); expect(readerProgress(i)).toBe("Chapter 142, read Oct 3, 2026"); expect(itemDetails(i)).toEqual(["Someone", 2020, "Chapter 142, read Oct 3, 2026"]); const book = note("library-item", { ...base, medium: "light novel" }, { bookProgress: { title: "X", percent: 62 } }); expect(readerProgress(book)).toBe("62% read"); }); it("lets the note's own progress win, and shows the same line when nothing is synced", () => { const own = note("library-item", { ...base, progress: "16/18 volumes" }, { chapterProgress: { chapter: 3 } }); expect(itemDetails(own)).toEqual(["Someone", 2020, "16/18 volumes"]); const bare = note("library-item", base); expect(itemDetails(bare)).toEqual(["Someone", 2020]); expect(readerProgress(note("library-item", base, { bookProgress: { percent: 0 } }))).toBeUndefined(); }); }); describe("Store options with synced stock", () => { const lineData = (options: unknown[]) => ({ category: "Tabletop", mode: "made to order", channel: "here", summary: "", priceTiers: [], options }); const stock = { filamentStock: [ { group: "Filament", options: [ { name: "Jade White", hex: "#f5f5dc", inStock: true }, { name: "Bambu Green", hex: "#00ae42", inStock: false }, { name: "Charcoal", hex: "#2b2b2b", inStock: true }, ], }, ], }; it("is exactly the note's options when nothing is synced", () => { const opts = [ { group: "Filament", name: "Jade White", hex: "#ffffff", inStock: true }, { group: "Finish", name: "Matte", inStock: false }, ]; expect(optionGroups(note("product-line", lineData(opts)))).toEqual([ ["Filament", [opts[0]]], ["Finish", [opts[1]]], ]); // Left out with nothing synced: in stock, as before the field was optional. expect(optionGroups(note("product-line", lineData([{ group: "Finish", name: "Gloss" }])))[0]![1][0]!.inStock).toBe(true); }); it("sits under the note: note values win, gaps take the synced value, unlisted in-stock colors follow", () => { const l = note( "product-line", lineData([ { group: "Filament", name: "Jade White" }, { group: "Filament", name: "Bambu Green", inStock: true }, { group: "Filament", name: "Galaxy Black" }, { group: "Finish", name: "Matte", inStock: true }, ]), stock, ); const [[g, filament], [, finish]] = optionGroups(l) as [string, ReturnType[number][1]][]; expect(g).toBe("Filament"); expect(filament).toEqual([ { group: "Filament", name: "Jade White", hex: "#f5f5dc", inStock: true }, // The note says in stock: the note wins over the source. { group: "Filament", name: "Bambu Green", hex: "#00ae42", inStock: true }, // Not at the source: out of stock. { group: "Filament", name: "Galaxy Black", inStock: false }, { group: "Filament", name: "Charcoal", hex: "#2b2b2b", inStock: true }, ]); expect(finish).toEqual([{ group: "Finish", name: "Matte", inStock: true }]); }); });