From 405cef0225c314b4162d587c74cfce6b65a7b257 Mon Sep 17 00:00:00 2001 From: Charley DAVID Date: Tue, 4 Sep 2018 17:14:20 -0300 Subject: [PATCH] Add support for validating functions inside schema's rules (#2151) * Add specs, defaults, and documentation to next/previous rule properties * Add comparator function support for kind/object, type, data, and mark --- docs/reference/slate/schema.md | 84 +++++++++++++++++++ packages/slate/src/models/schema.js | 34 +++++++- .../custom/child-kind-invalid-function.js | 34 ++++++++ .../custom/child-type-invalid-function.js | 32 +++++++ .../first-child-kind-invalid-function.js | 30 +++++++ .../first-child-type-invalid-function.js | 35 ++++++++ .../last-child-kind-invalid-function.js | 30 +++++++ .../last-child-type-invalid-function.js | 35 ++++++++ .../schema/custom/next-kind-invalid-custom.js | 38 +++++++++ .../custom/next-kind-invalid-default.js | 32 +++++++ .../custom/next-kind-invalid-function.js | 32 +++++++ .../schema/custom/next-type-invalid-custom.js | 36 ++++++++ .../custom/next-type-invalid-default.js | 28 +++++++ .../custom/next-type-invalid-function.js | 28 +++++++ .../custom/node-data-invalid-function.js | 28 +++++++ .../custom/node-mark-invalid-function.js | 31 +++++++ .../custom/node-object-invalid-function.js | 25 ++++++ .../custom/node-type-invalid-function.js | 25 ++++++ .../custom/parent-kind-invalid-function.js | 33 ++++++++ .../custom/parent-type-invalid-function.js | 30 +++++++ .../custom/previous-kind-invalid-custom.js | 38 +++++++++ .../custom/previous-kind-invalid-default.js | 32 +++++++ .../custom/previous-kind-invalid-function.js | 32 +++++++ .../custom/previous-type-invalid-custom.js | 36 ++++++++ .../custom/previous-type-invalid-default.js | 28 +++++++ .../custom/previous-type-invalid-function.js | 28 +++++++ 26 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 packages/slate/test/schema/custom/child-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/child-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/first-child-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/first-child-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/last-child-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/last-child-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/next-kind-invalid-custom.js create mode 100644 packages/slate/test/schema/custom/next-kind-invalid-default.js create mode 100644 packages/slate/test/schema/custom/next-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/next-type-invalid-custom.js create mode 100644 packages/slate/test/schema/custom/next-type-invalid-default.js create mode 100644 packages/slate/test/schema/custom/next-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/node-data-invalid-function.js create mode 100644 packages/slate/test/schema/custom/node-mark-invalid-function.js create mode 100644 packages/slate/test/schema/custom/node-object-invalid-function.js create mode 100644 packages/slate/test/schema/custom/node-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/parent-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/parent-type-invalid-function.js create mode 100644 packages/slate/test/schema/custom/previous-kind-invalid-custom.js create mode 100644 packages/slate/test/schema/custom/previous-kind-invalid-default.js create mode 100644 packages/slate/test/schema/custom/previous-kind-invalid-function.js create mode 100644 packages/slate/test/schema/custom/previous-type-invalid-custom.js create mode 100644 packages/slate/test/schema/custom/previous-type-invalid-default.js create mode 100644 packages/slate/test/schema/custom/previous-type-invalid-function.js diff --git a/docs/reference/slate/schema.md b/docs/reference/slate/schema.md index 5fc3a7c0b0..49e397322e 100644 --- a/docs/reference/slate/schema.md +++ b/docs/reference/slate/schema.md @@ -154,6 +154,24 @@ Will determine whether the node is treated as a "void" node or not, making its c Will validate the last child node against a [`match`](#match). +### `next` + +`Object|Array` + +```js +{ + next: { type: 'quote' }, +} +``` + +```js +{ + next: [{ type: 'quote' }, { type: 'paragraph' }], +} +``` + +Will validate the next sibling node against a [`match`](#match). + ### `nodes` `Array` @@ -231,6 +249,24 @@ For more information on the arguments passed to `normalize`, see the [Normalizin Will validate a node's parent against a [`match`](#match). +### `previous` + +`Object|Array` + +```js +{ + previous: { type: 'quote' }, +} +``` + +```js +{ + previous: [{ type: 'quote' }, { type: 'paragraph' }], +} +``` + +Will validate the previous sibling node against a [`match`](#match). + ### `text` `RegExp|Function` @@ -380,6 +416,30 @@ Raised when the `object` property of the last child node is invalid, when a spec Raised when the `type` property of the last child node is invalid, when a specific `last` rule was defined in a schema. +### `'next_sibling_object_invalid'` + +```js +{ + next: Node, + node: Node, + rule: Object, +} +``` + +Raised when the `object` property of the next sibling node is invalid, when a specific `next` rule was defined in a schema. + +### `'next_sibling_type_invalid'` + +```js +{ + next: Node, + node: Node, + rule: Object, +} +``` + +Raised when the `type` property of the next sibling node is invalid, when a specific `next` rule was defined in a schema. + ### `'node_data_invalid'` ```js @@ -451,3 +511,27 @@ Raised when the `object` property of the parent of a node is invalid, when a spe ``` Raised when the `type` property of the parent of a node is invalid, when a specific `parent` rule was defined in a schema. + +### `'previous_sibling_object_invalid'` + +```js +{ + previous: Node, + node: Node, + rule: Object, +} +``` + +Raised when the `object` property of the previous sibling node is invalid, when a specific `previous` rule was defined in a schema. + +### `'previous_sibling_type_invalid'` + +```js +{ + previous: Node, + node: Node, + rule: Object, +} +``` + +Raised when the `type` property of the previous sibling node is invalid, when a specific `previous` rule was defined in a schema. diff --git a/packages/slate/src/models/schema.js b/packages/slate/src/models/schema.js index 234e968e44..23d3a9cd5d 100644 --- a/packages/slate/src/models/schema.js +++ b/packages/slate/src/models/schema.js @@ -386,7 +386,7 @@ class Schema extends Record(DEFAULTS) { */ function defaultNormalize(change, error) { - const { code, node, child, key, mark } = error + const { code, node, child, next, previous, key, mark } = error switch (code) { case 'child_object_invalid': @@ -403,6 +403,24 @@ function defaultNormalize(change, error) { : change.removeNodeByKey(child.key, { normalize: false }) } + case 'previous_sibling_object_invalid': + case 'previous_sibling_type_invalid': { + return previous.object === 'text' && + node.object === 'block' && + node.nodes.size === 1 + ? change.removeNodeByKey(node.key, { normalize: false }) + : change.removeNodeByKey(previous.key, { normalize: false }) + } + + case 'next_sibling_object_invalid': + case 'next_sibling_type_invalid': { + return next.object === 'text' && + node.object === 'block' && + node.nodes.size === 1 + ? change.removeNodeByKey(node.key, { normalize: false }) + : change.removeNodeByKey(next.key, { normalize: false }) + } + case 'child_required': case 'node_text_invalid': case 'parent_object_invalid': @@ -493,12 +511,14 @@ function validateRules(object, rule, rules, options = {}) { function validateObject(node, rule) { if (rule.object == null) return if (rule.object === node.object) return + if (typeof rule.object === 'function' && rule.object(node.object)) return return fail('node_object_invalid', { rule, node }) } function validateType(node, rule) { if (rule.type == null) return if (rule.type === node.type) return + if (typeof rule.type === 'function' && rule.type(node.type)) return return fail('node_type_invalid', { rule, node }) } @@ -506,6 +526,11 @@ function validateData(node, rule) { if (rule.data == null) return if (node.data == null) return + if (typeof rule.data === 'function') { + if (rule.data(node.data)) return + return fail('node_data_invalid', { rule, node }) + } + for (const key in rule.data) { const fn = rule.data[key] const value = node.data && node.data.get(key) @@ -520,7 +545,12 @@ function validateMarks(node, rule) { const marks = node.getMarks().toArray() for (const mark of marks) { - const valid = rule.marks.some(def => def.type === mark.type) + const valid = rule.marks.some( + def => + typeof def.type === 'function' + ? def.type(mark.type) + : def.type === mark.type + ) if (valid) continue return fail('node_mark_invalid', { rule, node, mark }) } diff --git a/packages/slate/test/schema/custom/child-kind-invalid-function.js b/packages/slate/test/schema/custom/child-kind-invalid-function.js new file mode 100644 index 0000000000..bc7270ecd2 --- /dev/null +++ b/packages/slate/test/schema/custom/child-kind-invalid-function.js @@ -0,0 +1,34 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + nodes: [ + { + match: [{ object: o => ['inline', 'text'].includes(o) }], + }, + ], + }, + }, +} + +export const input = ( + + + + text + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/child-type-invalid-function.js b/packages/slate/test/schema/custom/child-type-invalid-function.js new file mode 100644 index 0000000000..96819ae64a --- /dev/null +++ b/packages/slate/test/schema/custom/child-type-invalid-function.js @@ -0,0 +1,32 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + nodes: [ + { + match: [{ type: t => t === 'paragraph' }], + }, + ], + }, + }, +} + +export const input = ( + + + + + + + +) + +export const output = ( + + + +) diff --git a/packages/slate/test/schema/custom/first-child-kind-invalid-function.js b/packages/slate/test/schema/custom/first-child-kind-invalid-function.js new file mode 100644 index 0000000000..2ccaddc043 --- /dev/null +++ b/packages/slate/test/schema/custom/first-child-kind-invalid-function.js @@ -0,0 +1,30 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + first: [{ object: o => o === 'text' }], + }, + }, +} + +export const input = ( + + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/first-child-type-invalid-function.js b/packages/slate/test/schema/custom/first-child-type-invalid-function.js new file mode 100644 index 0000000000..884b6a56ce --- /dev/null +++ b/packages/slate/test/schema/custom/first-child-type-invalid-function.js @@ -0,0 +1,35 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + first: { type: t => t === 'paragraph' }, + }, + }, +} + +export const input = ( + + + + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/last-child-kind-invalid-function.js b/packages/slate/test/schema/custom/last-child-kind-invalid-function.js new file mode 100644 index 0000000000..4439cec0bb --- /dev/null +++ b/packages/slate/test/schema/custom/last-child-kind-invalid-function.js @@ -0,0 +1,30 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + last: [{ object: o => o === 'text' }], + }, + }, +} + +export const input = ( + + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/last-child-type-invalid-function.js b/packages/slate/test/schema/custom/last-child-type-invalid-function.js new file mode 100644 index 0000000000..fe74d438c4 --- /dev/null +++ b/packages/slate/test/schema/custom/last-child-type-invalid-function.js @@ -0,0 +1,35 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: {}, + quote: { + last: { type: t => t === 'paragraph' }, + }, + }, +} + +export const input = ( + + + + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/next-kind-invalid-custom.js b/packages/slate/test/schema/custom/next-kind-invalid-custom.js new file mode 100644 index 0000000000..3fced31c40 --- /dev/null +++ b/packages/slate/test/schema/custom/next-kind-invalid-custom.js @@ -0,0 +1,38 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + next: [{ object: 'inline' }, { object: 'text' }], + normalize: (change, { code, next }) => { + if (code == 'next_sibling_object_invalid') { + change.unwrapBlockByKey(next.key, 'paragraph') + } + }, + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/next-kind-invalid-default.js b/packages/slate/test/schema/custom/next-kind-invalid-default.js new file mode 100644 index 0000000000..952c18b415 --- /dev/null +++ b/packages/slate/test/schema/custom/next-kind-invalid-default.js @@ -0,0 +1,32 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + next: [{ object: 'text' }], + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + +) diff --git a/packages/slate/test/schema/custom/next-kind-invalid-function.js b/packages/slate/test/schema/custom/next-kind-invalid-function.js new file mode 100644 index 0000000000..5d8d56e4c1 --- /dev/null +++ b/packages/slate/test/schema/custom/next-kind-invalid-function.js @@ -0,0 +1,32 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + next: [{ object: o => o === 'text' }], + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + +) diff --git a/packages/slate/test/schema/custom/next-type-invalid-custom.js b/packages/slate/test/schema/custom/next-type-invalid-custom.js new file mode 100644 index 0000000000..da3f8a3978 --- /dev/null +++ b/packages/slate/test/schema/custom/next-type-invalid-custom.js @@ -0,0 +1,36 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + next: [{ type: 'paragraph' }], + normalize: (change, { code, next }) => { + if (code == 'next_sibling_type_invalid') { + change.wrapBlockByKey(next.key, 'paragraph') + } + }, + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/next-type-invalid-default.js b/packages/slate/test/schema/custom/next-type-invalid-default.js new file mode 100644 index 0000000000..d57f86ca0e --- /dev/null +++ b/packages/slate/test/schema/custom/next-type-invalid-default.js @@ -0,0 +1,28 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + next: [{ type: 'paragraph' }], + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/next-type-invalid-function.js b/packages/slate/test/schema/custom/next-type-invalid-function.js new file mode 100644 index 0000000000..9d3f40cc05 --- /dev/null +++ b/packages/slate/test/schema/custom/next-type-invalid-function.js @@ -0,0 +1,28 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + next: [{ type: t => t === 'paragraph' }], + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/node-data-invalid-function.js b/packages/slate/test/schema/custom/node-data-invalid-function.js new file mode 100644 index 0000000000..1de89bcef5 --- /dev/null +++ b/packages/slate/test/schema/custom/node-data-invalid-function.js @@ -0,0 +1,28 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + data: d => d.get('thing') === 'valid', + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/node-mark-invalid-function.js b/packages/slate/test/schema/custom/node-mark-invalid-function.js new file mode 100644 index 0000000000..fb38335de5 --- /dev/null +++ b/packages/slate/test/schema/custom/node-mark-invalid-function.js @@ -0,0 +1,31 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + marks: [{ type: t => ['bold', 'underline'].includes(t) }], + }, + }, +} + +export const input = ( + + + + one two three + + + +) + +export const output = ( + + + + one two three + + + +) diff --git a/packages/slate/test/schema/custom/node-object-invalid-function.js b/packages/slate/test/schema/custom/node-object-invalid-function.js new file mode 100644 index 0000000000..e4df5b24ec --- /dev/null +++ b/packages/slate/test/schema/custom/node-object-invalid-function.js @@ -0,0 +1,25 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + object: o => o === 'inline', + }, + }, +} + +export const input = ( + + + invalid + + +) + +export const output = ( + + + +) diff --git a/packages/slate/test/schema/custom/node-type-invalid-function.js b/packages/slate/test/schema/custom/node-type-invalid-function.js new file mode 100644 index 0000000000..64c7e521c1 --- /dev/null +++ b/packages/slate/test/schema/custom/node-type-invalid-function.js @@ -0,0 +1,25 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + type: t => t === 'impossible', + }, + }, +} + +export const input = ( + + + invalid + + +) + +export const output = ( + + + +) diff --git a/packages/slate/test/schema/custom/parent-kind-invalid-function.js b/packages/slate/test/schema/custom/parent-kind-invalid-function.js new file mode 100644 index 0000000000..91974a364c --- /dev/null +++ b/packages/slate/test/schema/custom/parent-kind-invalid-function.js @@ -0,0 +1,33 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + inlines: { + link: { + parent: { object: o => o === 'block' }, + }, + }, +} + +export const input = ( + + + + + one + + + + +) + +export const output = ( + + + + + + + +) diff --git a/packages/slate/test/schema/custom/parent-type-invalid-function.js b/packages/slate/test/schema/custom/parent-type-invalid-function.js new file mode 100644 index 0000000000..f37dcb00f0 --- /dev/null +++ b/packages/slate/test/schema/custom/parent-type-invalid-function.js @@ -0,0 +1,30 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + list: {}, + item: { + parent: { type: t => t === 'list' }, + }, + }, +} + +export const input = ( + + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-custom.js b/packages/slate/test/schema/custom/previous-kind-invalid-custom.js new file mode 100644 index 0000000000..51806aecce --- /dev/null +++ b/packages/slate/test/schema/custom/previous-kind-invalid-custom.js @@ -0,0 +1,38 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + previous: [{ object: 'inline' }, { object: 'text' }], + normalize: (change, { code, previous }) => { + if (code == 'previous_sibling_object_invalid') { + change.unwrapBlockByKey(previous.key, 'paragraph') + } + }, + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-default.js b/packages/slate/test/schema/custom/previous-kind-invalid-default.js new file mode 100644 index 0000000000..9b89a6590f --- /dev/null +++ b/packages/slate/test/schema/custom/previous-kind-invalid-default.js @@ -0,0 +1,32 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + previous: [{ object: 'text' }], + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-function.js b/packages/slate/test/schema/custom/previous-kind-invalid-function.js new file mode 100644 index 0000000000..6a17ea716b --- /dev/null +++ b/packages/slate/test/schema/custom/previous-kind-invalid-function.js @@ -0,0 +1,32 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + image: { + previous: [{ object: o => o === 'text' }], + }, + }, +} + +export const input = ( + + + + + + + + +) + +export const output = ( + + + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-type-invalid-custom.js b/packages/slate/test/schema/custom/previous-type-invalid-custom.js new file mode 100644 index 0000000000..1d01870306 --- /dev/null +++ b/packages/slate/test/schema/custom/previous-type-invalid-custom.js @@ -0,0 +1,36 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + previous: [{ type: 'paragraph' }], + normalize: (change, { code, previous }) => { + if (code == 'previous_sibling_type_invalid') { + change.wrapBlockByKey(previous.key, 'paragraph') + } + }, + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-type-invalid-default.js b/packages/slate/test/schema/custom/previous-type-invalid-default.js new file mode 100644 index 0000000000..0e40604dd5 --- /dev/null +++ b/packages/slate/test/schema/custom/previous-type-invalid-default.js @@ -0,0 +1,28 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + previous: [{ type: 'paragraph' }], + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + +) diff --git a/packages/slate/test/schema/custom/previous-type-invalid-function.js b/packages/slate/test/schema/custom/previous-type-invalid-function.js new file mode 100644 index 0000000000..dfa8134017 --- /dev/null +++ b/packages/slate/test/schema/custom/previous-type-invalid-function.js @@ -0,0 +1,28 @@ +/** @jsx h */ + +import h from '../../helpers/h' + +export const schema = { + blocks: { + paragraph: { + previous: [{ type: t => t === 'paragraph' }], + }, + }, +} + +export const input = ( + + + + + + +) + +export const output = ( + + + + + +)