Ctrl K
ComunidadGuías…

tests/presenceCore.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// The presence core (shared/presence/{reduce,policy,render}.ts): many tabs
// reduced to one activity, the privacy policy that decides what may leave the
// process, and the card Discord is handed. The policy is the product's
// promise — a path never leaves, an opted-out note never shows its title — so
// every refusal it makes is pinned here.

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { presenceAsset, PRESENCE_STALE_MS } from "../shared/presence/config.ts";
import { cardsLeftPhrase } from "../shared/presence/copy.ts";
import { applyPolicy, titleOnly } from "../shared/presence/policy.ts";
import { holdReport, reduceReports, validateReport } from "../shared/presence/reduce.ts";
import { renderActivity, sameActivity, vaultNameOf, type RenderContext } from "../shared/presence/render.ts";
import { PRESENCE_DEFAULTS, type Activity, type PresenceEffective, type PresenceReport, type PublicActivity } from "../shared/presence/types.ts";

const T0 = 1_700_000_000_000;
const ON: PresenceEffective = { ...PRESENCE_DEFAULTS, mode: "title", clientId: "123456789012345678" };
const report = (over: Partial<PresenceReport> = {}): PresenceReport => ({ client: "tab-aaaaaa", kind: "writing", idleMs: 0, ...over });
const none = () => ({ optOut: false, excluded: false });

describe("reduceReports", () => {
  it("the most recently ACTIVE client wins, not the most recent post", () => {
    const busy = holdReport(report({ client: "tab-busy01", path: "A.md", idleMs: 1_000 }), T0);
    const heartbeat = holdReport(report({ client: "tab-bkgd01", kind: "library", idleMs: 300_000 }), T0 + 5_000);
    const a = reduceReports([busy, heartbeat], T0 + 6_000, ON, null, T0);
    assert.equal(a?.kind, "writing");
    assert.deepEqual(a?.subject, { path: "A.md", title: "A" });
  });

  it("drops a client silent for longer than the stale window; none left is null", () => {
    const old = holdReport(report({ path: "A.md" }), T0);
    assert.equal(reduceReports([old], T0 + PRESENCE_STALE_MS + 1, ON, null, T0), null);
    assert.equal(reduceReports([], T0, ON, null, T0), null);
  });

  it("idle: clear gives null, show gives Idle with no subject", () => {
    const h = holdReport(report({ path: "A.md", idleMs: 10 * 60_000 }), T0);
    assert.equal(reduceReports([h], T0, ON, null, T0), null);
    const idle = reduceReports([h], T0, { ...ON, idle: "show" }, null, T0);
    assert.deepEqual(idle, { kind: "idle", subject: null, cardsLeft: null, subjectSince: T0, sessionSince: T0 });
    const short = holdReport(report({ path: "A.md", idleMs: 2 * 60_000 }), T0);
    assert.equal(reduceReports([short], T0, { ...ON, idleMinutes: 2 }, null, T0), null);
    assert.equal(reduceReports([short], T0, { ...ON, idleMinutes: 3 }, null, T0)?.kind, "writing");
  });

  it("subjectSince is kept while the subject stays and reset when it moves; sessionSince never moves", () => {
    const first = reduceReports([holdReport(report({ path: "A.md", cardsLeft: 3 }), T0)], T0, ON, null, T0 - 99);
    assert.equal(first?.subjectSince, T0);
    const same = reduceReports([holdReport(report({ path: "A.md", cardsLeft: 2 }), T0 + 9)], T0 + 10, ON, first, T0 - 99);
    assert.equal(same?.subjectSince, T0);
    assert.equal(same?.cardsLeft, 2);
    const other = reduceReports([holdReport(report({ path: "B.md" }), T0 + 19)], T0 + 20, ON, same, T0 - 99);
    assert.equal(other?.subjectSince, T0 + 20);
    const kind = reduceReports([holdReport(report({ kind: "reading", path: "B.md" }), T0 + 29)], T0 + 30, ON, other, T0 - 99);
    assert.equal(kind?.subjectSince, T0 + 30, "the same note in another kind is a new subject");
    assert.equal(kind?.sessionSince, T0 - 99);
  });
});

describe("validateReport", () => {
  it("accepts a full report", () => {
    assert.deepEqual(validateReport({ client: "abcdefgh", kind: "orbits", path: "Decks/Kanji.md", cardsLeft: 12, idleMs: 0 }), {
      client: "abcdefgh",
      kind: "orbits",
      path: "Decks/Kanji.md",
      cardsLeft: 12,
      idleMs: 0,
    });
  });

  it("names the first problem", () => {
    const ok = { client: "abcdefgh", kind: "writing", idleMs: 0 };
    const bad: [unknown, RegExp][] = [
      [null, /object/],
      [[1], /object/],
      [{ ...ok, client: "short" }, /client/],
      [{ ...ok, client: "has space!" }, /client/],
      [{ ...ok, client: "x".repeat(65) }, /client/],
      [{ ...ok, kind: "idle" }, /kind/],
      [{ ...ok, path: "" }, /path/],
      [{ ...ok, path: "x".repeat(1001) }, /path/],
      [{ ...ok, path: "a\0b.md" }, /NUL/],
      [{ ...ok, path: "/etc/passwd" }, /relative/],
      [{ ...ok, path: "C:\\Users\\me.md" }, /relative/],
      [{ ...ok, path: "../out.md" }, /climb/],
      [{ ...ok, title: "t".repeat(201) }, /title/],
      [{ ...ok, cardsLeft: 1.5 }, /cardsLeft/],
      [{ ...ok, cardsLeft: 100_001 }, /cardsLeft/],
      [{ ...ok, cardsLeft: -1 }, /cardsLeft/],
      [{ ...ok, idleMs: -1 }, /idleMs/],
      [{ ...ok, idleMs: 2e9 }, /idleMs/],
      [{ client: "abcdefgh", kind: "writing" }, /idleMs/],
    ];
    for (const [body, re] of bad) {
      const got = validateReport(body);
      assert.equal(typeof got, "string", JSON.stringify(body));
      assert.match(got as string, re);
    }
  });
});

describe("applyPolicy", () => {
  const act = (over: Partial<Activity> = {}): Activity => ({
    kind: "writing",
    subject: { path: "Books/Deep/Chapter 3.md", title: "Chapter 3" },
    cardsLeft: null,
    subjectSince: T0 + 5,
    sessionSince: T0,
    ...over,
  });

  it("off → null, whatever the activity", () => {
    assert.equal(applyPolicy(act(), { ...ON, mode: "off" }, none), null);
    assert.equal(applyPolicy(null, ON, none), null);
  });

  it("title mode shows the title; activity mode strips it", () => {
    assert.equal(applyPolicy(act(), ON, none)?.title, "Chapter 3");
    assert.equal(applyPolicy(act(), { ...ON, mode: "activity" }, none)?.title, null);
  });

  it("an opted-out or excluded note loses its title and keeps its kind", () => {
    for (const f of [{ optOut: true, excluded: false }, { optOut: false, excluded: true }]) {
      const pub = applyPolicy(act(), ON, () => f);
      assert.deepEqual(pub, { kind: "writing", title: null, cardsLeft: null, since: T0 + 5 });
    }
  });

  it("a path never leaves: a title that IS a path is cut to its name", () => {
    const pub = applyPolicy(act({ subject: { path: "x.md", title: "Books/Deep/Chapter 3.md" } }), ON, none);
    assert.equal(pub?.title, "Chapter 3");
    assert.equal(titleOnly("Books/Deep/Chapter 3.md"), "Chapter 3");
    assert.equal(titleOnly("C:\\Vault\\Drafts\\Plan.tex"), "Plan");
    assert.equal(titleOnly("  Sketch.excalidraw.md "), "Sketch");
    assert.equal(titleOnly("book.EPUB"), "book");
    assert.equal(titleOnly("y".repeat(300)).length, 128);
    assert.equal(titleOnly("😀".repeat(100)).length, 128, "no half surrogate pair at the cut");
  });

  it("the timer follows `elapsed`, and counts pass through", () => {
    assert.equal(applyPolicy(act(), { ...ON, elapsed: "off" }, none)?.since, null);
    assert.equal(applyPolicy(act(), { ...ON, elapsed: "subject" }, none)?.since, T0 + 5);
    assert.equal(applyPolicy(act(), { ...ON, elapsed: "session" }, none)?.since, T0);
    assert.equal(applyPolicy(act({ kind: "orbits", cardsLeft: 7 }), ON, none)?.cardsLeft, 7);
  });
});

describe("renderActivity", () => {
  const ctx = (over: Partial<RenderContext> = {}): RenderContext => ({
    lang: "en",
    vaultName: "alchemy",
    siteUrl: "https://example.org",
    publicLayout: "blog",
    settings: ON,
    ...over,
  });
  const pub = (over: Partial<PublicActivity> = {}): PublicActivity => ({ kind: "writing", title: "Chapter 3", cardsLeft: null, since: T0, ...over });

  it("English: the kind, the title, the mark and the glyph, the timer", () => {
    assert.deepEqual(renderActivity(pub(), ctx()), {
      type: 0,
      details: "Writing",
      state: "Chapter 3",
      timestamps: { start: T0 },
      assets: {
        large_image: "https://zahakj.github.io/astrolabe/brand/presence/mark.png",
        small_image: "https://zahakj.github.io/astrolabe/brand/presence/pen.png",
        small_text: "Writing",
      },
    });
  });

  it("Arabic, with Western digits only", () => {
    const a = renderActivity(pub({ kind: "orbits", title: null, cardsLeft: 12 }), ctx({ lang: "ar" }));
    assert.equal(a.details, "يراجع البطاقات");
    assert.equal(a.state, "12 بطاقة متبقية");
    const all = JSON.stringify(a) + [0, 1, 2, 5, 10, 11, 250].map((n) => cardsLeftPhrase(n, "ar")).join(" ");
    assert.doesNotMatch(all, /[٠-٩۰-۹]/);
    assert.equal(cardsLeftPhrase(2, "ar"), "بطاقتان متبقيتان");
    assert.equal(cardsLeftPhrase(5, "ar"), "5 بطاقات متبقية");
    assert.equal(cardsLeftPhrase(1, "en"), "1 card left");
  });

  it("the second line falls back to the count, then the vault name when allowed", () => {
    assert.equal(renderActivity(pub({ title: null, cardsLeft: 3 }), ctx()).state, "3 cards left");
    assert.equal(renderActivity(pub({ title: null }), ctx()).state, undefined);
    const named = renderActivity(pub({ title: null }), ctx({ settings: { ...ON, vaultName: true } }));
    assert.equal(named.state, "alchemy");
    assert.equal(named.assets?.large_text, "alchemy");
    const both = renderActivity(pub({ kind: "orbits", cardsLeft: 4 }), ctx());
    assert.equal(both.state, "Chapter 3");
    assert.equal(both.assets?.small_text, "4 cards left", "the count moves to the glyph's tooltip");
  });

  it("one glyph per kind, uploaded keys when asked", () => {
    const glyphs = { writing: "pen", reading: "page", book: "book", orbits: "cards", drawing: "brush", library: "shelf", browsing: "compass", idle: "moon" } as const;
    for (const [kind, glyph] of Object.entries(glyphs)) {
      const a = renderActivity(pub({ kind: kind as PublicActivity["kind"] }), ctx({ settings: { ...ON, assets: "uploaded" } }));
      assert.equal(a.assets?.small_image, glyph);
      assert.equal(a.assets?.large_image, "mark");
    }
    assert.equal(presenceAsset("pen", "site"), "https://zahakj.github.io/astrolabe/brand/presence/pen.png");
  });

  it("no timer when since is null", () => {
    assert.equal(renderActivity(pub({ since: null }), ctx()).timestamps, undefined);
  });

  it("the button only with the setting, an https URL and a blog or designed layout", () => {
    const on = { ...ON, button: true };
    assert.deepEqual(renderActivity(pub(), ctx({ settings: on })).buttons, [{ label: "Read the blog", url: "https://example.org" }]);
    assert.deepEqual(renderActivity(pub(), ctx({ settings: on, lang: "ar", publicLayout: "designed" })).buttons?.[0].label, "اقرأ المدونة");
    assert.equal(renderActivity(pub(), ctx()).buttons, undefined);
    assert.equal(renderActivity(pub(), ctx({ settings: on, siteUrl: "http://example.org" })).buttons, undefined);
    assert.equal(renderActivity(pub(), ctx({ settings: on, siteUrl: null })).buttons, undefined);
    assert.equal(renderActivity(pub(), ctx({ settings: on, publicLayout: "app" })).buttons, undefined);
    assert.equal(renderActivity(pub(), ctx({ settings: on, siteUrl: `https://e.org/${"p".repeat(520)}` })).buttons, undefined);
  });

  it("every string within Discord's limits", () => {
    const a = renderActivity(pub({ title: "t".repeat(500) }), ctx({ vaultName: "v".repeat(500), settings: { ...ON, vaultName: true } }));
    assert.equal(a.state?.length, 128);
    assert.equal(a.assets?.large_text?.length, 128);
  });

  it("sameActivity ignores key order and sees real changes", () => {
    const a = renderActivity(pub(), ctx());
    const b = JSON.parse(JSON.stringify({ assets: a.assets, details: a.details, type: a.type, timestamps: a.timestamps, state: a.state }));
    assert.equal(sameActivity(a, b), true);
    assert.equal(sameActivity(a, renderActivity(pub({ title: "Chapter 4" }), ctx())), false);
    assert.equal(sameActivity(null, null), true);
    assert.equal(sameActivity(a, null), false);
  });

  it("vaultNameOf is the folder's name", () => {
    assert.equal(vaultNameOf("/home/me/Documents/alchemy"), "alchemy");
    assert.equal(vaultNameOf("/home/me/alchemy/"), "alchemy");
    assert.equal(vaultNameOf("C:\\Users\\me\\Vault"), "Vault");
  });
});