6dcc19ce59
git-sync's converter-core (src/lib) was a byte-identical duplicate of the new @docmost/prosemirror-markdown package (created in the previous commit). Switch git-sync to consume the package and delete its copy — ending the duplication that the whole #293 effort targets. Pure no-op: NO format/behavior change. - git-sync depends on @docmost/prosemirror-markdown (workspace:*); engine (stabilize/push/pull) + src/index barrel + 12 engine tests re-point their converter imports to the package. - Delete git-sync/src/lib (8 files) and the 23 duplicate converter-core test files + their fixtures — the converter and its ~440 tests now live once, in the package. git-sync keeps only its ENGINE tests, which exercise the converter through the package (the no-op proof). Kept roundtrip-helpers.ts (an engine test imports firstDivergence from it; pure helper, no double-run). - Added docmostExtensions to the package barrel (a kept engine schema-validity test needs it). Verified: editor-ext + prosemirror-markdown + git-sync all tsc EXIT 0; git-sync vitest 28 files, 268 passed, 0 failures (engine cycle/roundtrip/push/ pull/reconcile green = no-op proof); prosemirror-markdown vitest still 443 passed | 1 expected-fail; pnpm --frozen-lockfile EXIT 0; no ../lib refs remain in git-sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
3.2 KiB
TypeScript
72 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildVaultLayout, type PageNode } from '../src/engine/layout.js';
|
|
import { classifyRenameMoves } from '../src/engine/push.js';
|
|
import type {
|
|
ClassifyRenameMovesDeps,
|
|
MetaSide,
|
|
RenameMoveAction,
|
|
} from '../src/engine/push.js';
|
|
import type { DocmostMdMeta } from '@docmost/prosemirror-markdown';
|
|
|
|
// RED-TEAM finding #4 (two facets):
|
|
// (a) buildVaultLayout disambiguation is ORDER-DEPENDENT: which of two
|
|
// equally-titled root pages keeps the bare stem (and which gets the
|
|
// ` ~slugId` suffix) depends purely on input array order. The layout is
|
|
// supposed to be a deterministic function of the page SET, so reordering
|
|
// the input must not move the suffix onto a different page.
|
|
// (b) The page title derived from a DISAMBIGUATED filename ('Report ~a1.md')
|
|
// never strips the cosmetic ` ~slugId` suffix, so a pure disambiguation
|
|
// file-rename is mis-classified as a real title RENAME that would push the
|
|
// suffix ('Report ~a1') back into Docmost as the page's actual title.
|
|
|
|
describe('redteam #4a — buildVaultLayout is stable under input reorder', () => {
|
|
it('keeps the same stem for page A regardless of input order', () => {
|
|
const A: PageNode = { id: 'A', title: 'Report', slugId: 'a1', parentPageId: null };
|
|
const B: PageNode = { id: 'B', title: 'Report', slugId: 'b2', parentPageId: null };
|
|
|
|
const l1 = buildVaultLayout([A, B]);
|
|
const l2 = buildVaultLayout([B, A]);
|
|
|
|
// Identity (pageId A) must resolve to the same file stem no matter how the
|
|
// flat page list happened to be ordered.
|
|
expect(l2.get('A')?.stem).toBe(l1.get('A')?.stem);
|
|
});
|
|
});
|
|
|
|
describe('redteam #4b — disambiguation suffix is not a title change', () => {
|
|
// Mirror production push.ts `titleFromPath` EXACTLY: the synthetic native meta
|
|
// sets `title = baseName(path) without ".md"`. This is the real derivation the
|
|
// injected `metaAt` carries in `main`.
|
|
function titleFromPath(path: string): string {
|
|
const slash = path.lastIndexOf('/');
|
|
const base = slash < 0 ? path : path.slice(slash + 1);
|
|
return base.endsWith('.md') ? base.slice(0, -3) : base;
|
|
}
|
|
|
|
function deps(): ClassifyRenameMovesDeps {
|
|
const metaAt = (path: string, _side: MetaSide): DocmostMdMeta | null => ({
|
|
version: 1,
|
|
title: titleFromPath(path),
|
|
pageId: 'p1',
|
|
});
|
|
// Same enclosing folder (root) on both sides -> no reparent.
|
|
const resolveParentPageId = (_path: string, _side: MetaSide): string | null => null;
|
|
return { metaAt, resolveParentPageId };
|
|
}
|
|
|
|
it('does NOT emit a rename when only a ~slugId suffix was appended', () => {
|
|
// A sibling collision appeared, so the file 'Report.md' was relocated to the
|
|
// disambiguated 'Report ~a1.md'. The page TITLE in Docmost is still 'Report'.
|
|
const rms: RenameMoveAction[] = [
|
|
{ pageId: 'p1', oldPath: 'Report.md', newPath: 'Report ~a1.md' },
|
|
];
|
|
|
|
const [classified] = classifyRenameMoves(rms, deps());
|
|
|
|
// Desired behaviour: a pure disambiguation file-rename is cosmetic/local and
|
|
// must NOT be pushed as a title change. (If any rename WERE emitted it must
|
|
// carry the real title 'Report', never the suffixed 'Report ~a1'.)
|
|
expect(classified.rename).toBeUndefined();
|
|
});
|
|
});
|