mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 14:41:23 +02:00
Remove fast-deep-equal (#4276)
* Remove fast-deep-equal in favour of a custom slate-specific equality check that only supports nested objects * Add array comparison to deep equal. Fix bug with multiple nested objects.
This commit is contained in:
5
.changeset/polite-readers-talk.md
Normal file
5
.changeset/polite-readers-talk.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'slate': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Switched from `fast-deep-equal` to a custom deep equality check. This restores the ability for text nodes with mark values set to `undefined` to merge with text nodes missing those keys.
|
@@ -16,7 +16,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/esrever": "^0.2.0",
|
"@types/esrever": "^0.2.0",
|
||||||
"esrever": "^0.2.0",
|
"esrever": "^0.2.0",
|
||||||
"fast-deep-equal": "^3.1.3",
|
|
||||||
"immer": "^8.0.1",
|
"immer": "^8.0.1",
|
||||||
"is-plain-object": "^3.0.0",
|
"is-plain-object": "^3.0.0",
|
||||||
"tiny-warning": "^1.0.3"
|
"tiny-warning": "^1.0.3"
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import isPlainObject from 'is-plain-object'
|
import isPlainObject from 'is-plain-object'
|
||||||
import isEqual from 'fast-deep-equal'
|
|
||||||
import { Range } from '..'
|
import { Range } from '..'
|
||||||
import { ExtendedType } from './custom-types'
|
import { ExtendedType } from './custom-types'
|
||||||
|
import { isDeepEqual } from '../utils/deep-equal'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Text` objects represent the nodes that contain the actual text content of a
|
* `Text` objects represent the nodes that contain the actual text content of a
|
||||||
@@ -27,8 +27,10 @@ export interface TextInterface {
|
|||||||
export const Text: TextInterface = {
|
export const Text: TextInterface = {
|
||||||
/**
|
/**
|
||||||
* Check if two text nodes are equal.
|
* Check if two text nodes are equal.
|
||||||
|
*
|
||||||
|
* When loose is set, the text is not compared. This is
|
||||||
|
* used to check whether sibling text nodes can be merged.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
equals(
|
equals(
|
||||||
text: Text,
|
text: Text,
|
||||||
another: Text,
|
another: Text,
|
||||||
@@ -42,7 +44,7 @@ export const Text: TextInterface = {
|
|||||||
return rest
|
return rest
|
||||||
}
|
}
|
||||||
|
|
||||||
return isEqual(
|
return isDeepEqual(
|
||||||
loose ? omitText(text) : text,
|
loose ? omitText(text) : text,
|
||||||
loose ? omitText(another) : another
|
loose ? omitText(another) : another
|
||||||
)
|
)
|
||||||
|
46
packages/slate/src/utils/deep-equal.ts
Normal file
46
packages/slate/src/utils/deep-equal.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import isPlainObject from 'is-plain-object'
|
||||||
|
|
||||||
|
/*
|
||||||
|
Custom deep equal comparison for Slate nodes.
|
||||||
|
|
||||||
|
We don't need general purpose deep equality;
|
||||||
|
Slate only supports plain values, Arrays, and nested objects.
|
||||||
|
Complex values nested inside Arrays are not supported.
|
||||||
|
|
||||||
|
Slate objects are designed to be serialised, so
|
||||||
|
missing keys are deliberately normalised to undefined.
|
||||||
|
*/
|
||||||
|
export const isDeepEqual = (
|
||||||
|
node: Record<string, any>,
|
||||||
|
another: Record<string, any>
|
||||||
|
): boolean => {
|
||||||
|
for (const key in node) {
|
||||||
|
const a = node[key]
|
||||||
|
const b = another[key]
|
||||||
|
if (isPlainObject(a)) {
|
||||||
|
if (!isDeepEqual(a, b)) return false
|
||||||
|
} else if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
|
if (a.length !== b.length) return false
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if (a !== b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Deep object equality is only necessary in one direction; in the reverse direction
|
||||||
|
we are only looking for keys that are missing.
|
||||||
|
As above, undefined keys are normalised to missing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (const key in another) {
|
||||||
|
if (node[key] === undefined && another[key] !== undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
@@ -37,6 +37,14 @@ describe('slate', () => {
|
|||||||
assert.deepEqual(editor.children, output.children)
|
assert.deepEqual(editor.children, output.children)
|
||||||
assert.deepEqual(editor.selection, output.selection)
|
assert.deepEqual(editor.selection, output.selection)
|
||||||
})
|
})
|
||||||
|
fixtures(__dirname, 'utils', ({ module }) => {
|
||||||
|
let { input, test, output } = module
|
||||||
|
if (Editor.isEditor(input)) {
|
||||||
|
input = withTest(input)
|
||||||
|
}
|
||||||
|
const result = test(input)
|
||||||
|
assert.deepEqual(result, output)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
const withTest = editor => {
|
const withTest = editor => {
|
||||||
const { isInline, isVoid } = editor
|
const { isInline, isVoid } = editor
|
||||||
|
20
packages/slate/test/utils/deep-equal/deep-equals.js
Normal file
20
packages/slate/test/utils/deep-equal/deep-equals.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: false },
|
||||||
|
},
|
||||||
|
objectB: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = true
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: true },
|
||||||
|
underline: { origin: 'inherited', value: false },
|
||||||
|
},
|
||||||
|
objectB: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: true },
|
||||||
|
underline: { origin: 'inherited', value: true },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = false
|
20
packages/slate/test/utils/deep-equal/deep-not-equal.js
Normal file
20
packages/slate/test/utils/deep-equal/deep-not-equal.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: false },
|
||||||
|
},
|
||||||
|
objectB: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: true,
|
||||||
|
italic: { origin: 'inherited', value: true },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = false
|
12
packages/slate/test/utils/deep-equal/simple-equals.js
Normal file
12
packages/slate/test/utils/deep-equal/simple-equals.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: { text: 'same text', bold: true },
|
||||||
|
objectB: { text: 'same text', bold: true },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = true
|
12
packages/slate/test/utils/deep-equal/simple-not-equal.js
Normal file
12
packages/slate/test/utils/deep-equal/simple-not-equal.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: { text: 'same text', bold: true },
|
||||||
|
objectB: { text: 'same text', bold: true, italic: true },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = false
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: {
|
||||||
|
text: 'same text',
|
||||||
|
},
|
||||||
|
objectB: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = true
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { isDeepEqual } from '../../../src/utils/deep-equal'
|
||||||
|
|
||||||
|
export const input = {
|
||||||
|
objectA: {
|
||||||
|
text: 'same text',
|
||||||
|
bold: undefined,
|
||||||
|
},
|
||||||
|
objectB: {
|
||||||
|
text: 'same text',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = ({ objectA, objectB }) => {
|
||||||
|
return isDeepEqual(objectA, objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const output = true
|
@@ -5725,7 +5725,7 @@ faker@^4.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
|
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
|
||||||
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
|
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
fast-deep-equal@^3.1.1:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
Reference in New Issue
Block a user