1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-17 12:41:44 +02:00

Improve normalize suppression to make less verbose and safer (#1549)

* change normalization can be set with setOperationFlag, and changes can be executed in sequence with automatic suppression with guaranteed document normalization at the end.

* responded to developer feedback by renaming execute to withMutations (to mirror immutable JS), implemented tests, updated documentation

* fixed typos discovered in review.

* fixed missing normalize flag usages and added withMutations to the schemas guide

* responded to developer feedback

* fixed lint errors and cleaned up code

* readd missing tests

* getFlag now allows options to override the change flags

* removed normalize restoration asserts from unit tests

* unit test cleanup
This commit is contained in:
CameronAckermanSEL
2018-01-26 11:32:37 -08:00
committed by Ian Storm Taylor
parent 00165a3155
commit ef5106e30f
11 changed files with 382 additions and 33 deletions

View File

@@ -112,3 +112,67 @@ This validation defines a very specific (honestly, useless) behavior, where if a
When you need this level of specificity, using the `validateNode` property of the editor or plugins is handy. When you need this level of specificity, using the `validateNode` property of the editor or plugins is handy.
However, only use it when you absolutely have to. And when you do, you need to be aware of its performance. `validateNode` will be called **every time the node changes**, so it should be as performant as possible. That's why the example above returns early, so that the smallest amount of work is done as possible each time it is called. However, only use it when you absolutely have to. And when you do, you need to be aware of its performance. `validateNode` will be called **every time the node changes**, so it should be as performant as possible. That's why the example above returns early, so that the smallest amount of work is done as possible each time it is called.
## Multi-step Normalizations
Some normalizations will require multiple `change` function calls in order to complete. But after calling the first change function, the resulting document will be normalized, changing it out from under you. This can cause unintended behaviors.
Consider the following validation function that merges adjacent text nodes together.
Note: This functionality is already correctly implemented in slate-core so you don't need to put it in yourself!
```
/**
* Merge adjacent text nodes.
*
* @type {Object}
*/
validateNode(node) {
if (node.object != 'block' && node.object != 'inline') return
const invalids = node.nodes
.map((child, i) => {
const next = node.nodes.get(i + 1)
if (child.object != 'text') return
if (!next || next.object != 'text') return
return next
})
.filter(Boolean)
if (!invalids.size) return
return (change) => {
// Reverse the list to handle consecutive merges, since the earlier nodes
// will always exist after each merge.
invalids.reverse().forEach((n) => {
change.mergeNodeByKey(n.key)
})
}
}
```
There is actually a problem with this code. Because each `change` function call will cause nodes impacted by the mutation to be normalized, this can cause interruptions to carefully implemented sequences of `change` functions and may create performance problems or errors. The normalization logic in the above example will merge the last node in the invalids list together, but then it'll trigger another normalization and start over!
How can we deal with this? Well, normalization can be suppressed temporarily for multiple `change` function calls by using the `change.withoutNormalization` function. `withoutNormalization` accepts a function that takes a `change` object as a parameter, and executes the function while suppressing normalization. Once the function is done executing, the entire document is then normalized to pick up any unnnormalized transformations and ensure your document is in a normalized state.
The above validation function can then be written as below
```
/**
* Merge adjacent text nodes.
*
* @type {Object}
*/
validateNode(node) {
...
return (change) => {
change.withoutNormalization((c) => {
// Reverse the list to handle consecutive merges, since the earlier nodes
// will always exist after each merge.
invalids.reverse().forEach((n) => {
c.mergeNodeByKey(n.key)
})
});
}
}
```

View File

@@ -50,6 +50,33 @@ function onSomeEvent(event, change) {
} }
``` ```
### `withoutNormalization`
`withoutNormalization(customChange: Function) => Change`
This method calls the provided `customChange` function with the current instance of the `Change` object as the first argument. While `customChange` is executing, normalization is temporarily suppressed, but normalization will be executed once the `customChange` function completes execution.
The purpose of `withoutNormalization` is to allow a sequence of change operations that should not be interrupted by normalization. For example:
```js
/**
* Only allow block nodes in documents.
*
* @type {Object}
*/
validateNode(node) {
if (node.object != 'document') return
const invalids = node.nodes.filter(n => n.object != 'block')
if (!invalids.size) return
return (change) => {
change.withoutNormalization((c) => {
invalids.forEach((child) => {
c.removeNodeByKey(child.key)
})
})
}
}
```
## Current Value Changes ## Current Value Changes

View File

@@ -28,7 +28,7 @@ const Changes = {}
Changes.addMarkAtRange = (change, range, mark, options = {}) => { Changes.addMarkAtRange = (change, range, mark, options = {}) => {
if (range.isCollapsed) return if (range.isCollapsed) return
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const { startKey, startOffset, endKey, endOffset } = range const { startKey, startOffset, endKey, endOffset } = range
@@ -77,7 +77,7 @@ Changes.deleteAtRange = (change, range, options = {}) => {
// when you undo a delete, the expanded selection will be retained. // when you undo a delete, the expanded selection will be retained.
change.snapshotSelection() change.snapshotSelection()
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
let { startKey, startOffset, endKey, endOffset } = range let { startKey, startOffset, endKey, endOffset } = range
let { document } = value let { document } = value
@@ -350,7 +350,7 @@ Changes.deleteWordBackwardAtRange = (change, range, options) => {
*/ */
Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => { Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const { startKey, focusOffset } = range const { startKey, focusOffset } = range
@@ -536,7 +536,7 @@ Changes.deleteWordForwardAtRange = (change, range, options) => {
*/ */
Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => { Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const { startKey, focusOffset } = range const { startKey, focusOffset } = range
@@ -665,7 +665,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
Changes.insertBlockAtRange = (change, range, block, options = {}) => { Changes.insertBlockAtRange = (change, range, block, options = {}) => {
block = Block.create(block) block = Block.create(block)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
if (range.isExpanded) { if (range.isExpanded) {
change.deleteAtRange(range) change.deleteAtRange(range)
@@ -717,7 +717,7 @@ Changes.insertBlockAtRange = (change, range, block, options = {}) => {
*/ */
Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => { Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
// If the range is expanded, delete it first. // If the range is expanded, delete it first.
if (range.isExpanded) { if (range.isExpanded) {
@@ -830,7 +830,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
*/ */
Changes.insertInlineAtRange = (change, range, inline, options = {}) => { Changes.insertInlineAtRange = (change, range, inline, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
inline = Inline.create(inline) inline = Inline.create(inline)
if (range.isExpanded) { if (range.isExpanded) {
@@ -908,7 +908,7 @@ Changes.insertTextAtRange = (change, range, text, marks, options = {}) => {
Changes.removeMarkAtRange = (change, range, mark, options = {}) => { Changes.removeMarkAtRange = (change, range, mark, options = {}) => {
if (range.isCollapsed) return if (range.isCollapsed) return
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const texts = document.getTextsAtRange(range) const texts = document.getTextsAtRange(range)
@@ -938,7 +938,7 @@ Changes.removeMarkAtRange = (change, range, mark, options = {}) => {
*/ */
Changes.setBlockAtRange = (change, range, properties, options = {}) => { Changes.setBlockAtRange = (change, range, properties, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const blocks = document.getBlocksAtRange(range) const blocks = document.getBlocksAtRange(range)
@@ -959,7 +959,7 @@ Changes.setBlockAtRange = (change, range, properties, options = {}) => {
*/ */
Changes.setInlineAtRange = (change, range, properties, options = {}) => { Changes.setInlineAtRange = (change, range, properties, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const inlines = document.getInlinesAtRange(range) const inlines = document.getInlinesAtRange(range)
@@ -980,7 +980,7 @@ Changes.setInlineAtRange = (change, range, properties, options = {}) => {
*/ */
Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => { Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
if (range.isExpanded) { if (range.isExpanded) {
change.deleteAtRange(range, { normalize }) change.deleteAtRange(range, { normalize })
@@ -1014,7 +1014,7 @@ Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => {
*/ */
Changes.splitInlineAtRange = (change, range, height = Infinity, options = {}) => { Changes.splitInlineAtRange = (change, range, height = Infinity, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
if (range.isExpanded) { if (range.isExpanded) {
change.deleteAtRange(range, { normalize }) change.deleteAtRange(range, { normalize })
@@ -1053,7 +1053,7 @@ Changes.toggleMarkAtRange = (change, range, mark, options = {}) => {
mark = Mark.create(mark) mark = Mark.create(mark)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const marks = document.getActiveMarksAtRange(range) const marks = document.getActiveMarksAtRange(range)
@@ -1079,7 +1079,7 @@ Changes.toggleMarkAtRange = (change, range, mark, options = {}) => {
Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => { Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
properties = Node.createProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
let { document } = value let { document } = value
const blocks = document.getBlocksAtRange(range) const blocks = document.getBlocksAtRange(range)
@@ -1171,7 +1171,7 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => { Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
properties = Node.createProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const texts = document.getTextsAtRange(range) const texts = document.getTextsAtRange(range)
@@ -1218,7 +1218,7 @@ Changes.wrapBlockAtRange = (change, range, block, options = {}) => {
block = Block.create(block) block = Block.create(block)
block = block.set('nodes', block.nodes.clear()) block = block.set('nodes', block.nodes.clear())
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
@@ -1288,7 +1288,7 @@ Changes.wrapBlockAtRange = (change, range, block, options = {}) => {
Changes.wrapInlineAtRange = (change, range, inline, options = {}) => { Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
const { value } = change const { value } = change
let { document } = value let { document } = value
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { startKey, startOffset, endKey, endOffset } = range const { startKey, startOffset, endKey, endOffset } = range
if (range.isCollapsed) { if (range.isCollapsed) {
@@ -1397,7 +1397,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
*/ */
Changes.wrapTextAtRange = (change, range, prefix, suffix = prefix, options = {}) => { Changes.wrapTextAtRange = (change, range, prefix, suffix = prefix, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { startKey, endKey } = range const { startKey, endKey } = range
const start = range.collapseToStart() const start = range.collapseToStart()
let end = range.collapseToEnd() let end = range.collapseToEnd()

View File

@@ -26,7 +26,7 @@ const Changes = {}
Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => { Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
mark = Mark.create(mark) mark = Mark.create(mark)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -84,7 +84,7 @@ Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
*/ */
Changes.insertFragmentByKey = (change, key, index, fragment, options = {}) => { Changes.insertFragmentByKey = (change, key, index, fragment, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
fragment.nodes.forEach((node, i) => { fragment.nodes.forEach((node, i) => {
change.insertNodeByKey(key, index + i, node) change.insertNodeByKey(key, index + i, node)
@@ -107,7 +107,7 @@ Changes.insertFragmentByKey = (change, key, index, fragment, options = {}) => {
*/ */
Changes.insertNodeByKey = (change, key, index, node, options = {}) => { Changes.insertNodeByKey = (change, key, index, node, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -137,7 +137,8 @@ Changes.insertNodeByKey = (change, key, index, node, options = {}) => {
*/ */
Changes.insertTextByKey = (change, key, offset, text, marks, options = {}) => { Changes.insertTextByKey = (change, key, offset, text, marks, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -169,7 +170,7 @@ Changes.insertTextByKey = (change, key, offset, text, marks, options = {}) => {
*/ */
Changes.mergeNodeByKey = (change, key, options = {}) => { Changes.mergeNodeByKey = (change, key, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -208,7 +209,7 @@ Changes.mergeNodeByKey = (change, key, options = {}) => {
*/ */
Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => { Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -241,7 +242,7 @@ Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => {
Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => { Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => {
mark = Mark.create(mark) mark = Mark.create(mark)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -319,7 +320,7 @@ Changes.removeAllMarksByKey = (change, key, options = {}) => {
*/ */
Changes.removeNodeByKey = (change, key, options = {}) => { Changes.removeNodeByKey = (change, key, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -350,7 +351,7 @@ Changes.removeNodeByKey = (change, key, options = {}) => {
*/ */
Changes.removeTextByKey = (change, key, offset, length, options = {}) => { Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -408,7 +409,7 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
Changes.replaceNodeByKey = (change, key, newNode, options = {}) => { Changes.replaceNodeByKey = (change, key, newNode, options = {}) => {
newNode = Node.create(newNode) newNode = Node.create(newNode)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const node = document.getNode(key) const node = document.getNode(key)
@@ -436,7 +437,7 @@ Changes.replaceNodeByKey = (change, key, newNode, options = {}) => {
Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = {}) => { Changes.setMarkByKey = (change, key, offset, length, mark, properties, options = {}) => {
mark = Mark.create(mark) mark = Mark.create(mark)
properties = Mark.createProperties(properties) properties = Mark.createProperties(properties)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -469,7 +470,7 @@ Changes.setMarkByKey = (change, key, offset, length, mark, properties, options =
Changes.setNodeByKey = (change, key, properties, options = {}) => { Changes.setNodeByKey = (change, key, properties, options = {}) => {
properties = Node.createProperties(properties) properties = Node.createProperties(properties)
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const path = document.getPath(key) const path = document.getPath(key)
@@ -534,7 +535,7 @@ Changes.splitDescendantsByKey = (change, key, textKey, textOffset, options = {})
return return
} }
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
@@ -611,7 +612,7 @@ Changes.unwrapBlockByKey = (change, key, properties, options) => {
*/ */
Changes.unwrapNodeByKey = (change, key, options = {}) => { Changes.unwrapNodeByKey = (change, key, options = {}) => {
const { normalize = true } = options const normalize = change.getFlag('normalize', options)
const { value } = change const { value } = change
const { document } = value const { document } = value
const parent = document.getParent(key) const parent = document.getParent(key)

View File

@@ -48,7 +48,10 @@ class Change {
const { value } = attrs const { value } = attrs
this.value = value this.value = value
this.operations = new List() this.operations = new List()
this.flags = pick(attrs, ['merge', 'save']) this.flags = {
normalize: true,
...pick(attrs, ['merge', 'save', 'normalize'])
}
} }
/** /**
@@ -140,6 +143,27 @@ class Change {
return this return this
} }
/**
* Applies a series of change mutations and defers normalization until the end.
*
* @param {Function} customChange - function that accepts a change object and executes change operations
* @return {Change}
*/
withoutNormalization(customChange) {
const original = this.flags.normalize
this.setOperationFlag('normalize', false)
try {
customChange(this)
// if the change function worked then run normalization
this.normalizeDocument()
} finally {
// restore the flag to whatever it was
this.setOperationFlag('normalize', original)
}
return this
}
/** /**
* Set an operation flag by `key` to `value`. * Set an operation flag by `key` to `value`.
* *
@@ -153,6 +177,21 @@ class Change {
return this return this
} }
/**
* Get the `value` of the specified flag by its `key`. Optionally accepts an `options`
* object with override flags.
*
* @param {String} key
* @param {Object} options
* @return {Change}
*/
getFlag(key, options = {}) {
return options[key] !== undefined ?
options[key] :
this.flags[key]
}
/** /**
* Unset an operation flag by `key`. * Unset an operation flag by `key`.
* *

View File

@@ -15,6 +15,7 @@ describe('slate', () => {
require('./changes') require('./changes')
require('./history') require('./history')
require('./operations') require('./operations')
require('./models')
}) })
/** /**

View File

@@ -0,0 +1,48 @@
/** @jsx h */
import h from '../../helpers/h'
export const flags = { normalize: false }
export const schema = {
blocks: {
paragraph: {},
item: {
parent: { types: ['list'] },
nodes: [
{ objects: ['text'] }
]
},
list: {},
}
}
export const customChange = (change) => {
// this change function and schema are designed such that if
// validation takes place before both wrapBlock calls complete
// the node gets deleted by the default schema
// and causes a test failure
let target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'item')
target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'list')
}
export const input = (
<value>
<document>
<paragraph />
</document>
</value>
)
export const output = (
<value>
<document>
<list>
<item />
</list>
</document>
</value>
)

View File

@@ -0,0 +1,48 @@
/** @jsx h */
import h from '../../helpers/h'
export const flags = { normalize: true }
export const schema = {
blocks: {
paragraph: {},
item: {
parent: { types: ['list'] },
nodes: [
{ objects: ['text'] }
]
},
list: {},
}
}
export const customChange = (change) => {
// this change function and schema are designed such that if
// validation takes place before both wrapBlock calls complete
// the node gets deleted by the default schema
// and causes a test failure
let target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'item')
target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'list')
}
export const input = (
<value>
<document>
<paragraph />
</document>
</value>
)
export const output = (
<value>
<document>
<list>
<item />
</list>
</document>
</value>
)

View File

@@ -0,0 +1,40 @@
/** @jsx h */
import h from '../../helpers/h'
export const flags = { }
export const schema = {
blocks: {
paragraph: {},
item: {
parent: { types: ['list'] },
nodes: [
{ objects: ['text'] }
]
},
list: {},
}
}
export const customChange = (change) => {
// see if we can break the expected validation sequence by toggling
// the normalization option
const target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'item', { normalize: true })
}
export const input = (
<value>
<document>
<paragraph />
</document>
</value>
)
export const output = (
<value>
<document />
</value>
)

View File

@@ -0,0 +1,47 @@
/** @jsx h */
import h from '../../helpers/h'
export const flags = { }
export const schema = {
blocks: {
paragraph: {},
item: {
parent: { types: ['list'] },
nodes: [
{ objects: ['text'] }
]
},
list: {},
}
}
export const customChange = (change) => {
// this change function and schema are designed such that if
// validation takes place before both wrapBlock calls complete
// the node gets deleted by the default schema
// and causes a test failure
let target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'item')
target = change.value.document.nodes.get(0)
change.wrapBlockByKey(target.key, 'list')
}
export const input = (
<value>
<document>
<paragraph />
</document>
</value>
)
export const output = (
<value>
<document>
<list>
<item />
</list>
</document>
</value>
)

View File

@@ -0,0 +1,34 @@
import assert from 'assert'
import fs from 'fs'
import { Schema } from '../..'
import { basename, extname, resolve } from 'path'
/**
* Tests.
*/
describe('models', () => {
describe('change', () => {
describe('withoutNormalization', () => {
const testsDir = resolve(__dirname, 'change')
const tests = fs.readdirSync(testsDir).filter(t => t[0] != '.').map(t => basename(t, extname(t)))
for (const test of tests) {
it(test, async () => {
const module = require(resolve(testsDir, test))
const { input, output, schema, flags, customChange } = module
const s = Schema.create(schema)
const expected = output.toJSON()
const actual = input
.change(flags)
.setValue({ schema: s })
.withoutNormalization(customChange)
.value.toJSON()
assert.deepEqual(actual, expected)
})
}
})
})
})