From 67a695bb336f53e0b7356149b950f49a6f8654c2 Mon Sep 17 00:00:00 2001 From: Joe Anderson Date: Sun, 18 Feb 2024 21:52:43 +0000 Subject: [PATCH 1/2] Fix StringCharMapping key order dependency --- .changeset/great-buses-switch.md | 5 +++ .../utils/string-char-mapping.spec.ts | 10 ++++++ .../src/internal/utils/string-char-mapping.ts | 35 ++++++++----------- 3 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 .changeset/great-buses-switch.md create mode 100644 packages/diff/src/internal/utils/string-char-mapping.spec.ts 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..7ee7f6cd07 100644 --- a/packages/diff/src/internal/utils/string-char-mapping.ts +++ b/packages/diff/src/internal/utils/string-char-mapping.ts @@ -4,27 +4,30 @@ */ 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 _charGenerator = unusedCharGenerator(); private _charToNode: Map = new Map(); - private _keyToChar: Map = new Map(); + 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); + // 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]); this._charToNode.set(c, node); - return c; } @@ -37,14 +40,4 @@ export class StringCharMapping { 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; - } } From d523a071292ebfd3627c20c62b148fff9d6d0d2f Mon Sep 17 00:00:00 2001 From: Joe Anderson Date: Sun, 18 Feb 2024 21:57:09 +0000 Subject: [PATCH 2/2] Remove duplicated data on StringCharMapping --- packages/diff/src/internal/utils/string-char-mapping.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/diff/src/internal/utils/string-char-mapping.ts b/packages/diff/src/internal/utils/string-char-mapping.ts index 7ee7f6cd07..791a8ce066 100644 --- a/packages/diff/src/internal/utils/string-char-mapping.ts +++ b/packages/diff/src/internal/utils/string-char-mapping.ts @@ -10,7 +10,6 @@ import { unusedCharGenerator } from './unused-char-generator'; export class StringCharMapping { private _charGenerator = unusedCharGenerator(); - private _charToNode: Map = new Map(); private _mappedNodes: [TDescendant, string][] = []; public nodesToString(nodes: TDescendant[]): string { @@ -27,7 +26,6 @@ export class StringCharMapping { const c = this._charGenerator.next().value; this._mappedNodes.push([node, c]); - this._charToNode.set(c, node); return c; } @@ -36,8 +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; + const entry = this._mappedNodes.find(([_node, c2]) => c2 === c); + if (!entry) throw new Error(`No node found for char ${c}`); + return entry[0]; } }