Files
docmost-sync/test/divergence.test.ts
vvzvlad d9d8538846 test(sync): implement test-strategy Phase 1-2 (pure unit/golden/property), +102 tests
Work through test-strategy-report.md, high-ROI no-refactor subset (no regen).

- R-Infra: vitest resolve.alias docmost-client -> packages/docmost-client/src
  (fixes the dist-vs-src coverage artifact: canonicalize 0% -> real)
- R-Cfg-1: export parseArgs + tests
- canonicalize: align family / comment.resolved kept / link non-default +
  fixpoint & docsCanonicallyEqual reflexive/symmetric properties (0 -> 100%)
- markdown-converter golden matrix: columns/embed/audio/pdf, drawio data-align
  rule, inline-mark matrix, textAlign, escaping idempotence, table sanitization
  (61 -> 79%)
- schema parse-closures via generateJSON (TextStyle/comment/mention/Highlight/Column)
- node-ops (immutability, table edge cases, makeFreshId property), transforms
  (setCalloutRange/insertMarkerAfter/commentsToFootnotes + renumber property)
- stabilize normalize-on-write fixpoint (0 -> 100%); diff coarse-fallback;
  client-utils; firstDivergence; corpus fixtures details/columns/mention
- 593 -> 695 green; build clean; corpus STABLE

Deferred (Phase 3-4, refactor-gated): pull/collab/client-REST/git-merge integration.
2026-06-17 01:01:26 +03:00

79 lines
2.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { firstDivergence } from '../src/roundtrip.js';
describe('firstDivergence', () => {
it('returns null for equal nested objects', () => {
const a = { k1: { k2: 1, k3: [1, 2, 3] }, n: 'x' };
const b = { k1: { k2: 1, k3: [1, 2, 3] }, n: 'x' };
expect(firstDivergence(a, b)).toBeNull();
});
it('reports the correct path for a differing leaf', () => {
const a = { k1: { k2: 1 } };
const b = { k1: { k2: 2 } };
const d = firstDivergence(a, b);
expect(d).not.toBeNull();
expect(d!.path).toBe('$.k1.k2');
expect(d!.a).toBe(1);
expect(d!.b).toBe(2);
});
it('reports an array length mismatch at $.arr.length', () => {
const a = { arr: [1, 2, 3] };
const b = { arr: [1, 2] };
const d = firstDivergence(a, b);
expect(d).not.toBeNull();
expect(d!.path).toBe('$.arr.length');
expect(d!.a).toBe(3);
expect(d!.b).toBe(2);
});
it('reports a key present only in a', () => {
const a = { only: 'here', shared: 1 };
const b = { shared: 1 };
const d = firstDivergence(a, b);
expect(d).not.toBeNull();
expect(d!.path).toBe('$.only');
expect(d!.a).toBe('here');
expect(d!.b).toBeUndefined();
});
// --- branches NOT covered above (report §2) ---
it('reports a type mismatch (string vs number) at the current path', () => {
const d = firstDivergence({ k: '1' }, { k: 1 });
expect(d).toEqual({ path: '$.k', a: '1', b: 1 });
});
it('reports null-vs-object as a divergence (typeof object both, but one is null)', () => {
// typeof null === 'object', so this exercises the explicit `a===null` guard.
const d = firstDivergence({ k: null }, { k: { nested: true } });
expect(d).toEqual({ path: '$.k', a: null, b: { nested: true } });
});
it('reports array-vs-object as a divergence (aIsArr !== bIsArr)', () => {
const d = firstDivergence({ k: [1, 2] }, { k: { 0: 1, 1: 2 } });
expect(d).not.toBeNull();
expect(d!.path).toBe('$.k');
expect(Array.isArray(d!.a)).toBe(true);
expect(Array.isArray(d!.b)).toBe(false);
});
it('returns null for two equal primitives (the a === b fast path)', () => {
expect(firstDivergence(7, 7)).toBeNull();
expect(firstDivergence('same', 'same')).toBeNull();
expect(firstDivergence(null, null)).toBeNull();
expect(firstDivergence(true, true)).toBeNull();
});
it('reports a key present only in b (a-side undefined)', () => {
// The key union scans both objects, so a key only in `b` is detected with
// the a-side value undefined.
const d = firstDivergence({ shared: 1 }, { shared: 1, extra: 'b-only' });
expect(d).not.toBeNull();
expect(d!.path).toBe('$.extra');
expect(d!.a).toBeUndefined();
expect(d!.b).toBe('b-only');
});
});