117 lines
4.7 KiB
TypeScript
117 lines
4.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { buildPushPayload } from "../src/push.js";
|
|
import { MapNameResolver, nameUuidIndexFromEntries } from "../src/resolver.js";
|
|
import type { JournalEntry } from "../src/types.js";
|
|
|
|
// A synthetic "live" Foundry entry as relay /get would return it. Crucially it has
|
|
// ownership/pages + a sibling flag (not campaign-codex) that must survive the push.
|
|
const liveEntry: JournalEntry = {
|
|
name: "Fenris (old)",
|
|
_id: "aaa",
|
|
folder: "fld1",
|
|
pages: ["page1", "page2"],
|
|
ownership: { default: 0, gm: 3 },
|
|
flags: {
|
|
"campaign-codex": { type: "npc", image: "uploads/mardonar/old.webp", data: { tags: ["npc"], associates: ["JournalEntry.bbb"] } },
|
|
"some-other-module": { keep: "me" },
|
|
},
|
|
};
|
|
|
|
const NOTE = `---
|
|
type: npc
|
|
race: "[[Dwarf]]"
|
|
faction: "[[House Quche]]"
|
|
tags:
|
|
- status/alive
|
|
- npc
|
|
portrait: "[[Fenris_portrait.png]]"
|
|
foundry:
|
|
cc_uuid: JournalEntry.aaa
|
|
cc_type: npc
|
|
folder_path: Campaign Codex - NPCs
|
|
contentHash: deadbeef
|
|
syncedAt: 2026-06-20T00:00:00.000Z
|
|
---
|
|
|
|
# Fenris of House Quche
|
|
|
|
*A grizzled dwarf.*
|
|
|
|
## Background
|
|
|
|
Fenris serves [[House Quche]] and knows [[Joron The Crab]].
|
|
|
|
## Secrets
|
|
|
|
He hides a map.
|
|
`;
|
|
|
|
const resolver = new MapNameResolver(nameUuidIndexFromEntries([
|
|
{ name: "House Quche", uuid: "JournalEntry.qqq" },
|
|
{ name: "Joron The Crab", uuid: "JournalEntry.bbb" },
|
|
{ name: "Dwarf", uuid: "JournalEntry.race-dwarf" },
|
|
]));
|
|
|
|
describe("buildPushPayload", () => {
|
|
it("overrides name + flags.campaign-codex, spreads the live entry", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
expect(full.name).toBe("Fenris of House Quche");
|
|
expect(full._id).toBe("aaa"); // spread from live entry
|
|
expect(full.pages).toEqual(["page1", "page2"]); // spread preserved
|
|
expect(full.ownership).toEqual({ default: 0, gm: 3 });
|
|
// The full doc overrides `flags` wholesale (only campaign-codex); the sibling
|
|
// flag is NOT on `full` — but it survives in Foundry because the /update diff
|
|
// uses a dot-path merge and never echoes it (asserted below).
|
|
expect(full.flags?.["some-other-module"]).toBeUndefined();
|
|
expect(full.flags?.["campaign-codex"]?.type).toBe("npc");
|
|
});
|
|
|
|
it("the /update diff carries only name + flags.campaign-codex (no _id/pages/ownership)", () => {
|
|
const { diff } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
expect(Object.keys(diff).sort()).toEqual(["flags.campaign-codex", "name"]);
|
|
expect(diff.name).toBe("Fenris of House Quche");
|
|
expect(diff._id).toBeUndefined();
|
|
expect(diff.pages).toBeUndefined();
|
|
expect(diff.ownership).toBeUndefined();
|
|
// The diff does NOT carry the sibling flag (dot-path merge on the live doc keeps it).
|
|
expect(diff["some-other-module"]).toBeUndefined();
|
|
});
|
|
|
|
it("resolves [[links]] -> @UUID via the map resolver (not the DB)", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
const desc = full.flags?.["campaign-codex"]?.data?.description ?? "";
|
|
expect(desc).toContain("@UUID[JournalEntry.qqq]{House Quche}");
|
|
expect(desc).toContain("@UUID[JournalEntry.bbb]{Joron The Crab}");
|
|
});
|
|
|
|
it("image override wins over the live entry's existing image", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver, "uploads/mardonar/new.png");
|
|
expect(full.flags?.["campaign-codex"]?.image).toBe("uploads/mardonar/new.png");
|
|
});
|
|
|
|
it("image undefined keeps the live entry's existing image", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
expect(full.flags?.["campaign-codex"]?.image).toBe("uploads/mardonar/old.webp");
|
|
});
|
|
|
|
it("image null clears the image", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver, null);
|
|
expect(full.flags?.["campaign-codex"]?.image).toBeNull();
|
|
});
|
|
|
|
it("preserves associates from the live entry's data (spread)", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
expect(full.flags?.["campaign-codex"]?.data?.associates).toEqual(["JournalEntry.bbb"]);
|
|
});
|
|
|
|
it("status/* tags are dropped from the cc tags (matching the offline path)", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
expect(full.flags?.["campaign-codex"]?.data?.tags).toEqual(["npc"]);
|
|
});
|
|
|
|
it("secrets section becomes notes HTML", () => {
|
|
const { full } = buildPushPayload(NOTE, "Fenris of House Quche", liveEntry, resolver);
|
|
const notes = full.flags?.["campaign-codex"]?.data?.notes ?? "";
|
|
expect(notes).toContain("map");
|
|
});
|
|
}); |