From ca98fead1f81ff8f8f0fa9245d6df909206bf1cb Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 15 Jul 2026 16:18:07 +0200 Subject: [PATCH] add test case for custom AttributionsRenderer usecase --- src/utils/meta.js | 10 +++++++--- tests/attribution.tests.js | 31 +++++++++++++++++++++++++++++++ tests/updates.tests.js | 2 +- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/utils/meta.js b/src/utils/meta.js index 2d6b0d4f..be00029a 100644 --- a/src/utils/meta.js +++ b/src/utils/meta.js @@ -43,9 +43,13 @@ export const createContentIdsFromContentMap = contentMap => createContentIds( /** * @param {import('./Doc.js').Doc} ydoc + * @param {boolean} insertsContainDeletes - whether the returned `inserts` set includes content that + * the doc has already deleted. `true` yields a full structural snapshot (every inserted id); + * `false` yields only the doc's currently-visible content (`inserts − deletes`), which is what you + * want when the snapshot defines what should render. */ -export const createContentIdsFromDoc = ydoc => createContentIds( - createInsertSetFromStructStore(ydoc.store, false), +export const createContentIdsFromDoc = (ydoc, insertsContainDeletes) => createContentIds( + createInsertSetFromStructStore(ydoc.store, !insertsContainDeletes), createDeleteSetFromStructStore(ydoc.store) ) @@ -54,7 +58,7 @@ export const createContentIdsFromDoc = ydoc => createContentIds( * @param {import('./Doc.js').Doc} ydocNext */ export const createContentIdsFromDocDiff = (ydocPrev, ydocNext) => - excludeContentIds(createContentIdsFromDoc(ydocPrev), createContentIdsFromDoc(ydocNext)) + excludeContentIds(createContentIdsFromDoc(ydocPrev, true), createContentIdsFromDoc(ydocNext, true)) /** * @param {ContentIds} content diff --git a/tests/attribution.tests.js b/tests/attribution.tests.js index fc2964af..935efb16 100644 --- a/tests/attribution.tests.js +++ b/tests/attribution.tests.js @@ -2170,3 +2170,34 @@ export const testRdtBaseFormatClearInsideSuggestionDeletedParagraphCacheDrift = } t.assert(cached.equals(fresh), 'maintained .delta must equal a fresh deep render after a base format clear inside a suggestion-deleted paragraph') } + +/** + * + * + * @param {t.TestCase} _tc + */ +export const testAttributionRendererActiveChangesInDiff = _tc => { + const ydoc = new Y.Doc({ gc: false }) + const ytext = ydoc.get() + ytext.insert(0, 'hella') + const state1 = Y.createContentIdsFromDoc(ydoc, true) + ytext.applyDelta(delta.create().retain(4).delete(1).insert('o!').done()) + const state2 = Y.createContentIdsFromDoc(ydoc, true) + ytext.applyDelta(delta.create().retain(5).delete(1).insert(' world').done()) + // state3 captures the *visible* content (`insertsContainDeletes: false` → inserts already exclude + // deletes). Using it both as the intersection target and as `renderedContent` means: a change that + // was inserted-then-deleted by state3 (the '!' at clock 6) is no longer an active insert and drops + // out, and state3's deletions (the 'a' at clock 4) aren't "restored" so they keep their delete + // attribution instead of re-rendering as inserts. + const state3 = Y.createContentIdsFromDoc(ydoc, false) + ytext.applyDelta(delta.create().delete(11).insert('42').done()) + + // render the active changes that happened between state 1 and 2 within state 3. + const attrs = Y.createContentMapFromContentIds(Y.intersectContentIds(Y.excludeContentIds(state2, state1), state3), [], []) + const renderer = Y.createAttributionsRenderer(attrs, { renderedContent: state3.inserts }) + + const attrDelta = ytext.toDelta({ renderer }) + console.log(attrDelta.toJSON()) + t.compare(attrDelta, delta.create().insert('hell').insert('a', null, { delete: [] }).insert('o', null, { insert: [] }).insert(' world').done()) +} + diff --git a/tests/updates.tests.js b/tests/updates.tests.js index b90bb789..bfc12a09 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -396,7 +396,7 @@ export const testObfuscateUpdates = _tc => { export const testIntersectDoc = () => { const ydoc = new Y.Doc() ydoc.get().setAttr('k', 1) - const c1 = Y.createContentIdsFromDoc(ydoc) + const c1 = Y.createContentIdsFromDoc(ydoc, true) ydoc.get().setAttr('k', 2) const v1 = Y.intersectUpdateWithContentIds(Y.encodeStateAsUpdate(ydoc), c1)