Files
gitmost/packages/mcp/test/unit/recreate-transform-drift.test.mjs
claude code agent 227 e2b7ff10d9 test(mcp): media round-trip attrs, cookie parsing, anchor apply, recreate drift
Extract pure extractAuthTokenFromSetCookie from performLogin (behavior-identical)
so cookie parsing is unit-testable without a network login. Add round-trip
coverage for media attrs (width/height/align/drawio/escaping) the existing
suite omitted; applyAnchorInDoc selection/ambiguity/atom-break cases; and a
cross-copy drift guard proving the vendored editor-ext recreate-transform and
the @fellow npm copy used by diff.ts emit identical steps (apply(diff)==target).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 04:49:41 +03:00

140 lines
6.0 KiB
JavaScript

// CONTRACT / DRIFT GUARD: mcp diff vs the vendored editor-ext recreate-transform.
//
// packages/mcp/src/lib/diff.ts computes its document diff with
// `recreateTransform` from the published @fellow/prosemirror-recreate-transform
// package. Docmost's in-app history editor computes the SAME diff with its own
// vendored copy at
// packages/editor-ext/src/lib/recreate-transform/recreateTransform.ts.
// diff.ts's header comment claims the two are "identical" — if they ever drift,
// the headless mcp diff would stop matching what a user sees in the app.
//
// This test guards that claim two ways, on representative doc pairs, using the
// EXACT options diff.ts passes (complexSteps:false, wordDiffs:true,
// simplifyDiff:true):
// 1. invariant: each implementation's transform reproduces the target doc
// (apply(diff) == target);
// 2. cross-copy parity: both implementations emit the SAME step sequence, so a
// behavioral divergence between the two copies fails this test.
//
// The vendored copy is TypeScript, so it is transpiled to CommonJS at test time
// and required directly — the test runs the ACTUAL vendored source, not a stand-in.
import { test, before } from "node:test";
import assert from "node:assert/strict";
import ts from "typescript";
import fs from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { recreateTransform as fellowRecreate } from "@fellow/prosemirror-recreate-transform";
import { Node } from "@tiptap/pm/model";
import { docmostSchema } from "../../build/lib/docmost-schema.js";
const require = createRequire(import.meta.url);
const HERE = path.dirname(fileURLToPath(import.meta.url));
// .../packages/mcp/test/unit -> repo packages root.
const PACKAGES = path.resolve(HERE, "..", "..", "..");
const VENDOR_SRC = path.join(
PACKAGES,
"editor-ext",
"src",
"lib",
"recreate-transform",
);
// Emit transpiled CJS under mcp/build so Node resolves the hoisted deps
// (@tiptap/pm, rfc6902, diff) up the directory tree exactly as diff.js does.
const VENDOR_OUT = path.resolve(HERE, "..", "..", "build", "_vendored_editor_ext");
// The exact options the mcp diff pipeline uses (diff.ts).
const DIFF_OPTS = { complexSteps: false, wordDiffs: true, simplifyDiff: true };
let vendoredRecreate;
before(() => {
assert.ok(
fs.existsSync(VENDOR_SRC),
`vendored recreate-transform sources missing at ${VENDOR_SRC}`,
);
fs.rmSync(VENDOR_OUT, { recursive: true, force: true });
fs.mkdirSync(VENDOR_OUT, { recursive: true });
// Mark the output as CommonJS so relative `require("./x")` resolves to x.js.
fs.writeFileSync(
path.join(VENDOR_OUT, "package.json"),
JSON.stringify({ type: "commonjs" }),
);
for (const f of fs.readdirSync(VENDOR_SRC)) {
if (!f.endsWith(".ts")) continue;
const code = fs.readFileSync(path.join(VENDOR_SRC, f), "utf8");
const out = ts.transpileModule(code, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
},
});
fs.writeFileSync(path.join(VENDOR_OUT, f.replace(/\.ts$/, ".js")), out.outputText);
}
vendoredRecreate = require(path.join(VENDOR_OUT, "index.js")).recreateTransform;
assert.equal(typeof vendoredRecreate, "function", "vendored recreateTransform loaded");
});
// ---------------------------------------------------------------------------
// Builders + representative doc pairs covering the diff shapes diff.ts handles.
// ---------------------------------------------------------------------------
const t = (text, marks) => (marks ? { type: "text", text, marks } : { type: "text", text });
const para = (...c) => ({ type: "paragraph", content: c });
const doc = (...c) => ({ type: "doc", content: c });
const PAIRS = [
// word inserted mid-sentence
["insert word", doc(para(t("Hello world"))), doc(para(t("Hello brave world")))],
// whole block deleted
["delete block", doc(para(t("keep this")), para(t("remove this"))), doc(para(t("keep this")))],
// word removed mid-sentence
["delete word", doc(para(t("one two three"))), doc(para(t("one three")))],
// pure mark addition (complexSteps:false treats it as a content step)
["add mark", doc(para(t("plain"))), doc(para(t("plain", [{ type: "bold" }])))],
// two blocks swapped (reorder)
["reorder blocks", doc(para(t("a")), para(t("b"))), doc(para(t("b")), para(t("a")))],
// structural insert: an image node appears
[
"insert image",
doc(para(t("caption"))),
doc(para(t("caption")), { type: "image", attrs: { src: "/api/files/a.png", attachmentId: "i1" } }),
],
];
const stepsJSON = (tr) => JSON.stringify(tr.steps.map((s) => s.toJSON()));
for (const [label, fromJSON, toJSON] of PAIRS) {
test(`invariant: @fellow recreateTransform reproduces the target (${label})`, () => {
const from = Node.fromJSON(docmostSchema, fromJSON);
const to = Node.fromJSON(docmostSchema, toJSON);
const tr = fellowRecreate(from, to, DIFF_OPTS);
// apply(diff) == target, comparing schema-normalized JSON on both sides.
assert.equal(JSON.stringify(tr.doc.toJSON()), JSON.stringify(to.toJSON()));
});
test(`drift: @fellow and vendored editor-ext emit identical steps (${label})`, () => {
const mk = () => [
Node.fromJSON(docmostSchema, fromJSON),
Node.fromJSON(docmostSchema, toJSON),
];
const [fA, tA] = mk();
const [fB, tB] = mk();
const trFellow = fellowRecreate(fA, tA, DIFF_OPTS);
const trVendor = vendoredRecreate(fB, tB, DIFF_OPTS);
// Both must reach the same target...
const target = JSON.stringify(tA.toJSON());
assert.equal(JSON.stringify(trFellow.doc.toJSON()), target, "fellow reaches target");
assert.equal(JSON.stringify(trVendor.doc.toJSON()), target, "vendored reaches target");
// ...and, critically, via the SAME step sequence. A divergence in the two
// recreate-transform copies' algorithm would change the steps and fail here.
assert.equal(
stepsJSON(trVendor),
stepsJSON(trFellow),
`vendored editor-ext drifted from @fellow on "${label}"`,
);
});
}