test(story): cover chapter ordering, display modes, and book paging
This commit is contained in:
parent
f074149f6c
commit
185a7379f4
1 changed files with 114 additions and 0 deletions
|
|
@ -4,6 +4,8 @@ import config from "@/payload.config";
|
|||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import type { StoryBeat, StoryBeatState, StoryVoiceover, User } from "@/payload-types";
|
||||
import { markBeatRead, setBeatReaction } from "@/lib/story";
|
||||
import { sortBeats, chronicleDisplayOrder } from "@/lib/story/sortBeats";
|
||||
import { bookColumnOffset, bookPageCount } from "@/lib/story/bookLayout";
|
||||
|
||||
let payload: Payload;
|
||||
|
||||
|
|
@ -469,3 +471,115 @@ describe("Story beats", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sortBeats (chapter ordering)", () => {
|
||||
const baseBeat = {
|
||||
status: "published" as const,
|
||||
body: {} as StoryBeat["body"],
|
||||
};
|
||||
|
||||
function beat(overrides: Partial<StoryBeat> & { id: number }): StoryBeat {
|
||||
return { ...baseBeat, ...overrides } as unknown as StoryBeat;
|
||||
}
|
||||
|
||||
it("defaults to real creation date ascending, ignoring in-story timestamps", () => {
|
||||
// Fictional in-story dates deliberately out of release order.
|
||||
const prologue = beat({
|
||||
id: 1,
|
||||
title: "Little Green Light",
|
||||
createdAt: "2026-09-20T10:00:00Z",
|
||||
occurredAt: new Date("2156-03-18T08:00:00Z").toISOString(),
|
||||
});
|
||||
const followUp = beat({
|
||||
id: 2,
|
||||
title: "What Makes Us Human",
|
||||
createdAt: "2026-09-24T10:00:00Z",
|
||||
occurredAt: new Date("2034-06-12T08:00:00Z").toISOString(),
|
||||
});
|
||||
|
||||
const sorted = sortBeats([followUp, prologue]);
|
||||
expect(sorted.map((beat) => beat.id)).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it("puts explicitly ordered beats first, sorted by their order value", () => {
|
||||
const old = beat({ id: 1, createdAt: "2026-01-01T00:00:00Z" });
|
||||
const newer = beat({ id: 2, createdAt: "2026-02-01T00:00:00Z", order: 1 });
|
||||
const pinnedLast = beat({ id: 3, createdAt: "2026-03-01T00:00:00Z", order: 2 });
|
||||
// order = 0 is a valid curation value and must still lead the list.
|
||||
const pinnedFirst = beat({ id: 4, createdAt: "2026-04-01T00:00:00Z", order: 0 });
|
||||
|
||||
const sorted = sortBeats([old, pinnedFirst, newer, pinnedLast]);
|
||||
expect(sorted.map((beat) => beat.id)).toEqual([4, 2, 3, 1]);
|
||||
});
|
||||
|
||||
it("does not mutate the input array", () => {
|
||||
const a = beat({ id: 1, createdAt: "2026-01-01T00:00:00Z" });
|
||||
const b = beat({ id: 2, createdAt: "2026-02-01T00:00:00Z" });
|
||||
const input = [b, a];
|
||||
|
||||
sortBeats(input);
|
||||
expect(input.map((beat) => beat.id)).toEqual([2, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("chronicleDisplayOrder (page display)", () => {
|
||||
const baseBeat = {
|
||||
status: "published" as const,
|
||||
body: {} as StoryBeat["body"],
|
||||
};
|
||||
|
||||
function beat(overrides: Partial<StoryBeat> & { id: number }): StoryBeat {
|
||||
return { ...baseBeat, ...overrides } as unknown as StoryBeat;
|
||||
}
|
||||
|
||||
it("shows the newest chapter first by default, with chapter numbers still ascending", () => {
|
||||
const ch1 = beat({ id: 1, createdAt: "2026-09-20T10:00:00Z" });
|
||||
const ch2 = beat({ id: 2, createdAt: "2026-09-21T10:00:00Z" });
|
||||
const ch3 = beat({ id: 3, createdAt: "2026-09-22T10:00:00Z" });
|
||||
|
||||
const { beats, chapterById } = chronicleDisplayOrder([ch1, ch2, ch3]);
|
||||
|
||||
expect(beats.map((beat) => beat.id)).toEqual([3, 2, 1]);
|
||||
expect(chapterById.get(1)).toBe(1);
|
||||
expect(chapterById.get(2)).toBe(2);
|
||||
expect(chapterById.get(3)).toBe(3);
|
||||
});
|
||||
|
||||
it("switches to book mode when any beat is hand-ordered", () => {
|
||||
const a = beat({ id: 1, createdAt: "2026-09-20T10:00:00Z" });
|
||||
const b = beat({ id: 2, createdAt: "2026-09-21T10:00:00Z" });
|
||||
const c = beat({ id: 3, createdAt: "2026-09-22T10:00:00Z", order: 1 });
|
||||
|
||||
const { beats, chapterById } = chronicleDisplayOrder([a, b, c]);
|
||||
|
||||
expect(beats.map((beat) => beat.id)).toEqual([3, 1, 2]);
|
||||
expect(chapterById.get(3)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bookLayout (Book view paging)", () => {
|
||||
it("counts one page when content fits the viewport", () => {
|
||||
// One 2-up page: scrollWidth = column width = (1200 - 32) / 2 = 584.
|
||||
expect(bookPageCount(584, 1200, 2)).toBe(1);
|
||||
// scrollWidth == viewport width means both visible pages are exactly full: 2 pages.
|
||||
expect(bookPageCount(1200, 1200, 2)).toBe(2);
|
||||
});
|
||||
|
||||
it("counts pages from the measured multicol scrollWidth", () => {
|
||||
// 3 pages at 2-up: scrollWidth = 3 * pitch - gap = 3 * 616 - 32 = 1816.
|
||||
expect(bookPageCount(1816, 1200, 2)).toBe(3);
|
||||
// A lone right page (2.5 pages of content) still counts as its own page.
|
||||
expect(bookPageCount(1816 + 300, 1200, 2)).toBe(4);
|
||||
});
|
||||
|
||||
it("pays the gap once per page in single-column mode", () => {
|
||||
// 2 pages at 1-up, viewport 700: scrollWidth = 2 * 732 - 32 = 1432.
|
||||
expect(bookPageCount(1432, 700, 1)).toBe(2);
|
||||
});
|
||||
|
||||
it("computes the flip offset as pageIndex * pitch", () => {
|
||||
expect(bookColumnOffset(0, 1200, 2)).toBe(0);
|
||||
expect(bookColumnOffset(2, 1200, 2)).toBe(1232);
|
||||
expect(bookColumnOffset(1, 700, 1)).toBe(732);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue