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>
122 lines
4.3 KiB
TypeScript
122 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { readExisting } from '../src/engine/pull';
|
|
import { serializePageFile } from '@docmost/prosemirror-markdown';
|
|
|
|
// R-Pull-1 (test-strategy report §5): `readExisting` now takes injectable IO
|
|
// (`listTracked` / `readFile`), so its parsing + skip rules are unit-testable
|
|
// without a real git repo or filesystem. These tests pass fakes only — no git,
|
|
// no fs, no network. Identity is recovered from the native `gitmost_id`
|
|
// frontmatter (no more `docmost:meta`).
|
|
|
|
/** Build a valid native page file with a `gitmost_id` frontmatter. */
|
|
function withId(id: string, body = '# Title\nbody\n'): string {
|
|
return serializePageFile(id, body);
|
|
}
|
|
|
|
/** A fake `readFile` backed by an in-memory map (rejects on a missing key). */
|
|
function fakeReadFile(files: Record<string, string>) {
|
|
return async (rel: string): Promise<string> => {
|
|
if (!(rel in files)) {
|
|
throw Object.assign(new Error(`ENOENT: ${rel}`), { code: 'ENOENT' });
|
|
}
|
|
return files[rel];
|
|
};
|
|
}
|
|
|
|
describe('readExisting (R-Pull-1, injected IO)', () => {
|
|
it('recovers { pageId, relPath } for valid tracked files', async () => {
|
|
const files = {
|
|
'Space/A.md': withId('p1'),
|
|
'Space/Sub/B.md': withId('p2'),
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => Object.keys(files),
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
expect(result).toEqual([
|
|
{ pageId: 'p1', relPath: 'Space/A.md' },
|
|
{ pageId: 'p2', relPath: 'Space/Sub/B.md' },
|
|
]);
|
|
});
|
|
|
|
it('SKIPS a file with no frontmatter (plain hand-written markdown)', async () => {
|
|
const files = {
|
|
'tracked.md': withId('p1'),
|
|
'stray.md': '# Just a hand-written note\n\nNo frontmatter here.\n',
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => Object.keys(files),
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
// Only the engine-tracked file (with a gitmost_id) survives.
|
|
expect(result).toEqual([{ pageId: 'p1', relPath: 'tracked.md' }]);
|
|
});
|
|
|
|
it('SKIPS a file whose frontmatter has no gitmost_id key', async () => {
|
|
const files = {
|
|
'has-id.md': withId('keep'),
|
|
// A user's own frontmatter, but no gitmost_id -> not engine-tracked.
|
|
'no-id.md': '---\ntags: [note]\ntitle: untitled\n---\n\nbody\n',
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => Object.keys(files),
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
expect(result).toEqual([{ pageId: 'keep', relPath: 'has-id.md' }]);
|
|
});
|
|
|
|
it('SKIPS a file with an EMPTY gitmost_id value, does not throw', async () => {
|
|
const files = {
|
|
'good.md': withId('good'),
|
|
'blank.md': '---\ngitmost_id:\n---\n\nbody\n',
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => Object.keys(files),
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
expect(result).toEqual([{ pageId: 'good', relPath: 'good.md' }]);
|
|
});
|
|
|
|
it('does NOT throw when readFile REJECTS (tracked but missing) — treats it as skipped', async () => {
|
|
const files = {
|
|
'present.md': withId('present'),
|
|
// "ghost.md" is listed as tracked but absent from the file map -> reject.
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => ['present.md', 'ghost.md'],
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
// The rejection is swallowed; the present file still comes through.
|
|
expect(result).toEqual([{ pageId: 'present', relPath: 'present.md' }]);
|
|
});
|
|
|
|
it('returns an empty list when nothing is tracked', async () => {
|
|
const result = await readExisting({
|
|
listTracked: async () => [],
|
|
readFile: async () => {
|
|
throw new Error('should not be called');
|
|
},
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('combines all skip rules in one listing (only the valid files survive)', async () => {
|
|
const files = {
|
|
'ok1.md': withId('a'),
|
|
'no-meta.md': 'plain\n',
|
|
'no-id.md': '---\ntags: [x]\n---\n\nbody\n',
|
|
'blank.md': '---\ngitmost_id:\n---\n\nbody\n',
|
|
'ok2.md': withId('b'),
|
|
// missing.md rejects on read.
|
|
};
|
|
const result = await readExisting({
|
|
listTracked: async () => [...Object.keys(files), 'missing.md'],
|
|
readFile: fakeReadFile(files),
|
|
});
|
|
expect(result).toEqual([
|
|
{ pageId: 'a', relPath: 'ok1.md' },
|
|
{ pageId: 'b', relPath: 'ok2.md' },
|
|
]);
|
|
});
|
|
});
|