diff --git a/.changeset/great-buses-switch.md b/.changeset/great-buses-switch.md new file mode 100644 index 0000000000..e446aa546f --- /dev/null +++ b/.changeset/great-buses-switch.md @@ -0,0 +1,5 @@ +--- +"@udecode/plate-diff": patch +--- + +Fix: Node equivalency checking is incorrectly dependent on the key order of the node object diff --git a/packages/diff/src/internal/utils/string-char-mapping.spec.ts b/packages/diff/src/internal/utils/string-char-mapping.spec.ts new file mode 100644 index 0000000000..baee45458d --- /dev/null +++ b/packages/diff/src/internal/utils/string-char-mapping.spec.ts @@ -0,0 +1,10 @@ +import { StringCharMapping } from './string-char-mapping'; + +describe('StringCharMapping', () => { + it('treats nodes as equivalent regardless of key order', () => { + const map = new StringCharMapping(); + const c1 = map.nodeToChar({ type: 'a', children: [] }); + const c2 = map.nodeToChar({ children: [], type: 'a' }); + expect(c1).toBe(c2); + }); +}); diff --git a/packages/diff/src/internal/utils/string-char-mapping.ts b/packages/diff/src/internal/utils/string-char-mapping.ts index 48738fc5ad..791a8ce066 100644 --- a/packages/diff/src/internal/utils/string-char-mapping.ts +++ b/packages/diff/src/internal/utils/string-char-mapping.ts @@ -4,27 +4,28 @@ */ import { TDescendant } from '@udecode/plate-common'; +import isEqual from 'lodash/isEqual.js'; + +import { unusedCharGenerator } from './unused-char-generator'; export class StringCharMapping { - private _nextChar: string = 'A'; - private _charToNode: Map = new Map(); - private _keyToChar: Map = new Map(); + private _charGenerator = unusedCharGenerator(); + private _mappedNodes: [TDescendant, string][] = []; public nodesToString(nodes: TDescendant[]): string { return nodes.map(this.nodeToChar.bind(this)).join(''); } public nodeToChar(node: TDescendant): string { - const key = this.getKeyForNode(node); - - const existingChar = this._keyToChar.get(key); - if (existingChar) return existingChar; - - const c = this.getNextChar(); - - this._keyToChar.set(key, c); - this._charToNode.set(c, node); - + // Check for a previously assigned character + for (const [n, c] of this._mappedNodes) { + if (isEqual(n, node)) { + return c; + } + } + + const c = this._charGenerator.next().value; + this._mappedNodes.push([node, c]); return c; } @@ -33,18 +34,8 @@ export class StringCharMapping { } public charToNode(c: string): TDescendant { - const node = this._charToNode.get(c); - if (!node) throw new Error(`No node found for char ${c}`); - return node; - } - - private getKeyForNode(node: TDescendant): string { - return JSON.stringify(node); - } - - private getNextChar(): string { - const c = this._nextChar; - this._nextChar = String.fromCodePoint(this._nextChar.codePointAt(0)! + 1); - return c; + const entry = this._mappedNodes.find(([_node, c2]) => c2 === c); + if (!entry) throw new Error(`No node found for char ${c}`); + return entry[0]; } }