mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-06 23:36:31 +02:00
slate-hyperscript tests and decorations (#1777)
* initial simple decorations (mark-like), many tests added * allow decorators to be set by focus, anchor tags - add tests * handle one more edge case with decorations in hyperscript * apply prettier cleanup * apply linting rules * update changelog * ensure always normalize decoration ranges * reapply prettier after latest adjustments * update in response to review * drop unnecessarily committed add'l file * remove the need for explicit anchor, focus prop on decoration tags
This commit is contained in:
committed by
Ian Storm Taylor
parent
184722bdbf
commit
0dc2a4feab
@@ -9,6 +9,7 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
|||||||
* Accept `normalize` option for `<value>` tag. This allows to write
|
* Accept `normalize` option for `<value>` tag. This allows to write
|
||||||
invalid values, on purpose, for example to test validation logic.
|
invalid values, on purpose, for example to test validation logic.
|
||||||
* Fixed a bug that added extra text nodes. You would not encounter these if you always wrapped things in a `<value>` tag, that was running a normalization.
|
* Fixed a bug that added extra text nodes. You would not encounter these if you always wrapped things in a `<value>` tag, that was running a normalization.
|
||||||
|
* **Decorations** can now also be specified in hyperscript, similarly to specifying a selection. See the examples under `./test/decorations/`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -13,6 +13,42 @@ const ANCHOR = {}
|
|||||||
const CURSOR = {}
|
const CURSOR = {}
|
||||||
const FOCUS = {}
|
const FOCUS = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wrappers for decorator points, for comparison by instanceof,
|
||||||
|
* and for composition into ranges (anchor.combine(focus), etc)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DecoratorPoint {
|
||||||
|
constructor(key, marks) {
|
||||||
|
this._key = key
|
||||||
|
this.marks = marks
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
withPosition = offset => {
|
||||||
|
this.offset = offset
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
addOffset = offset => {
|
||||||
|
this.offset += offset
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
withKey = key => {
|
||||||
|
this.key = key
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
combine = focus => {
|
||||||
|
if (!(focus instanceof DecoratorPoint))
|
||||||
|
throw new Error('misaligned decorations')
|
||||||
|
return Range.create({
|
||||||
|
anchorKey: this.key,
|
||||||
|
focusKey: focus.key,
|
||||||
|
anchorOffset: this.offset,
|
||||||
|
focusOffset: focus.offset,
|
||||||
|
marks: this.marks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default Slate hyperscript creator functions.
|
* The default Slate hyperscript creator functions.
|
||||||
*
|
*
|
||||||
@@ -59,6 +95,22 @@ const CREATORS = {
|
|||||||
return nodes
|
return nodes
|
||||||
},
|
},
|
||||||
|
|
||||||
|
decoration(tagName, attributes, children) {
|
||||||
|
if (attributes.key) {
|
||||||
|
return new DecoratorPoint(attributes.key, [{ type: tagName }])
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = createChildren(children, { key: attributes.key })
|
||||||
|
nodes[0].__decorations = (nodes[0].__decorations || []).concat([
|
||||||
|
{
|
||||||
|
anchorOffset: 0,
|
||||||
|
focusOffset: nodes.reduce((len, n) => len + n.text.length, 0),
|
||||||
|
marks: [{ type: tagName }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
return nodes
|
||||||
|
},
|
||||||
|
|
||||||
selection(tagName, attributes, children) {
|
selection(tagName, attributes, children) {
|
||||||
return Range.create(attributes)
|
return Range.create(attributes)
|
||||||
},
|
},
|
||||||
@@ -68,6 +120,8 @@ const CREATORS = {
|
|||||||
const document = children.find(Document.isDocument)
|
const document = children.find(Document.isDocument)
|
||||||
let selection = children.find(Range.isRange) || Range.create()
|
let selection = children.find(Range.isRange) || Range.create()
|
||||||
const props = {}
|
const props = {}
|
||||||
|
let decorations = []
|
||||||
|
const partialDecorations = {}
|
||||||
|
|
||||||
// Search the document's texts to see if any of them have the anchor or
|
// Search the document's texts to see if any of them have the anchor or
|
||||||
// focus information saved, so we can set the selection.
|
// focus information saved, so we can set the selection.
|
||||||
@@ -85,6 +139,44 @@ const CREATORS = {
|
|||||||
props.isFocused = true
|
props.isFocused = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// now check for decorations and hoist them to the top
|
||||||
|
document.getTexts().forEach(text => {
|
||||||
|
if (text.__decorations != null) {
|
||||||
|
// add in all mark-like (keyless) decorations
|
||||||
|
decorations = decorations.concat(
|
||||||
|
text.__decorations.filter(d => d._key === undefined).map(d =>
|
||||||
|
Range.create({
|
||||||
|
...d,
|
||||||
|
anchorKey: text.key,
|
||||||
|
focusKey: text.key,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// store or combine partial decorations (keyed with anchor / focus)
|
||||||
|
text.__decorations
|
||||||
|
.filter(d => d._key !== undefined)
|
||||||
|
.forEach(partial => {
|
||||||
|
if (partialDecorations[partial._key]) {
|
||||||
|
decorations.push(
|
||||||
|
partialDecorations[partial._key].combine(
|
||||||
|
partial.withKey(text.key)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
delete partialDecorations[partial._key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
partialDecorations[partial._key] = partial.withKey(text.key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// should have no more parital decorations outstanding (all paired)
|
||||||
|
if (Object.keys(partialDecorations).length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Slate hyperscript must have both an anchor and focus defined for each keyed decorator.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.anchorKey && !props.focusKey) {
|
if (props.anchorKey && !props.focusKey) {
|
||||||
@@ -103,7 +195,16 @@ const CREATORS = {
|
|||||||
selection = selection.merge(props).normalize(document)
|
selection = selection.merge(props).normalize(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = Value.fromJSON({ data, document, selection }, { normalize })
|
let value = Value.fromJSON({ data, document, selection }, { normalize })
|
||||||
|
|
||||||
|
// apply any decorations built
|
||||||
|
if (decorations.length > 0) {
|
||||||
|
value = value
|
||||||
|
.change()
|
||||||
|
.setValue({ decorations: decorations.map(d => d.normalize(document)) })
|
||||||
|
.value
|
||||||
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -170,9 +271,10 @@ function createChildren(children, options = {}) {
|
|||||||
// Create a helper to update the current node while preserving any stored
|
// Create a helper to update the current node while preserving any stored
|
||||||
// anchor or focus information.
|
// anchor or focus information.
|
||||||
function setNode(next) {
|
function setNode(next) {
|
||||||
const { __anchor, __focus } = node
|
const { __anchor, __focus, __decorations } = node
|
||||||
if (__anchor != null) next.__anchor = __anchor
|
if (__anchor != null) next.__anchor = __anchor
|
||||||
if (__focus != null) next.__focus = __focus
|
if (__focus != null) next.__focus = __focus
|
||||||
|
if (__decorations != null) next.__decorations = __decorations
|
||||||
node = next
|
node = next
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +300,7 @@ function createChildren(children, options = {}) {
|
|||||||
// the existing node is empty, and the `key` option wasn't set, preserve the
|
// the existing node is empty, and the `key` option wasn't set, preserve the
|
||||||
// child's key when updating the node.
|
// child's key when updating the node.
|
||||||
if (Text.isText(child)) {
|
if (Text.isText(child)) {
|
||||||
const { __anchor, __focus } = child
|
const { __anchor, __focus, __decorations } = child
|
||||||
let i = node.text.length
|
let i = node.text.length
|
||||||
|
|
||||||
if (!options.key && node.text.length == 0) {
|
if (!options.key && node.text.length == 0) {
|
||||||
@@ -214,6 +316,20 @@ function createChildren(children, options = {}) {
|
|||||||
|
|
||||||
if (__anchor != null) node.__anchor = __anchor + length
|
if (__anchor != null) node.__anchor = __anchor + length
|
||||||
if (__focus != null) node.__focus = __focus + length
|
if (__focus != null) node.__focus = __focus + length
|
||||||
|
if (__decorations != null) {
|
||||||
|
node.__decorations = (node.__decorations || []).concat(
|
||||||
|
__decorations.map(
|
||||||
|
d =>
|
||||||
|
d instanceof DecoratorPoint
|
||||||
|
? d.addOffset(length)
|
||||||
|
: {
|
||||||
|
...d,
|
||||||
|
anchorOffset: d.anchorOffset + length,
|
||||||
|
focusOffset: d.focusOffset + length,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
length += child.text.length
|
length += child.text.length
|
||||||
}
|
}
|
||||||
@@ -221,6 +337,13 @@ function createChildren(children, options = {}) {
|
|||||||
// If the child is a selection object store the current position.
|
// If the child is a selection object store the current position.
|
||||||
if (child == ANCHOR || child == CURSOR) node.__anchor = length
|
if (child == ANCHOR || child == CURSOR) node.__anchor = length
|
||||||
if (child == FOCUS || child == CURSOR) node.__focus = length
|
if (child == FOCUS || child == CURSOR) node.__focus = length
|
||||||
|
|
||||||
|
// if child is a decorator point, store it as partial decorator
|
||||||
|
if (child instanceof DecoratorPoint) {
|
||||||
|
node.__decorations = (node.__decorations || []).concat([
|
||||||
|
child.withPosition(length),
|
||||||
|
])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Make sure the most recent node is added.
|
// Make sure the most recent node is added.
|
||||||
@@ -239,7 +362,7 @@ function createChildren(children, options = {}) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function resolveCreators(options) {
|
function resolveCreators(options) {
|
||||||
const { blocks = {}, inlines = {}, marks = {} } = options
|
const { blocks = {}, inlines = {}, marks = {}, decorators = {} } = options
|
||||||
|
|
||||||
const creators = {
|
const creators = {
|
||||||
...CREATORS,
|
...CREATORS,
|
||||||
@@ -258,6 +381,10 @@ function resolveCreators(options) {
|
|||||||
creators[key] = normalizeMark(key, marks[key])
|
creators[key] = normalizeMark(key, marks[key])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Object.keys(decorators).map(key => {
|
||||||
|
creators[key] = normalizeNode(key, decorators[key], 'decoration')
|
||||||
|
})
|
||||||
|
|
||||||
return creators
|
return creators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
125
packages/slate-hyperscript/test/custom/custom-tags.js
Normal file
125
packages/slate-hyperscript/test/custom/custom-tags.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
image: {
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inlines: {
|
||||||
|
link: 'link',
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
b: 'bold',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
A string of <b>bold</b> in a <link src="http://slatejs.org">Slate</link>{' '}
|
||||||
|
editor!
|
||||||
|
</paragraph>
|
||||||
|
<image src="https://..." />
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'A string of ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'bold',
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'bold',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' in a ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'inline',
|
||||||
|
type: 'link',
|
||||||
|
isVoid: false,
|
||||||
|
data: {
|
||||||
|
src: 'http://slatejs.org',
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'Slate',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' editor!',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true,
|
||||||
|
data: {
|
||||||
|
src: 'https://...',
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: '',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
87
packages/slate-hyperscript/test/decorations/across-blocks.js
Normal file
87
packages/slate-hyperscript/test/decorations/across-blocks.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
This is one <highlight key="a" />block.
|
||||||
|
</paragraph>
|
||||||
|
<paragraph>
|
||||||
|
This is block<highlight key="a" /> two.
|
||||||
|
</paragraph>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is one block.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is block two.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 12,
|
||||||
|
focusOffset: 13,
|
||||||
|
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||||
|
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
89
packages/slate-hyperscript/test/decorations/across-marks.js
Normal file
89
packages/slate-hyperscript/test/decorations/across-marks.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
b: 'bold',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a{' '}
|
||||||
|
<highlight>
|
||||||
|
paragraph <b>with</b>
|
||||||
|
</highlight>{' '}
|
||||||
|
a cursor position <cursor />(closed selection).
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is a paragraph ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'with',
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'bold',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' a cursor position (closed selection).',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 10,
|
||||||
|
focusOffset: 24,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
104
packages/slate-hyperscript/test/decorations/deep-anchors.js
Normal file
104
packages/slate-hyperscript/test/decorations/deep-anchors.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
ul: 'ul',
|
||||||
|
li: 'li',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Item <highlight key="a" />one.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Item<highlight key="a" /> two.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'ul',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'li',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'Item one.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'li',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'Item two.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 5,
|
||||||
|
focusOffset: 4,
|
||||||
|
anchorKey: input.document
|
||||||
|
.filterDescendants(n => n.type === 'li')
|
||||||
|
.get(0)
|
||||||
|
.getFirstText().key,
|
||||||
|
focusKey: input.document
|
||||||
|
.filterDescendants(n => n.type === 'li')
|
||||||
|
.get(1)
|
||||||
|
.getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
@@ -0,0 +1,89 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
b: 'bold',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a <highlight key="c" />paragraph{' '}
|
||||||
|
<b>
|
||||||
|
with<highlight key="c" />
|
||||||
|
</b>{' '}
|
||||||
|
a highlight.
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is a paragraph ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'with',
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'bold',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' a highlight.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 10,
|
||||||
|
focusOffset: 24,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
81
packages/slate-hyperscript/test/decorations/multiple.js
Normal file
81
packages/slate-hyperscript/test/decorations/multiple.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
lowlight: 'lowlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a <highlight>paragraph with</highlight> two{' '}
|
||||||
|
<lowlight>decorations</lowlight>.
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is a paragraph with two decorations.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 10,
|
||||||
|
focusOffset: 24,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchorOffset: 29,
|
||||||
|
focusOffset: 40,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'lowlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
100
packages/slate-hyperscript/test/decorations/nested.js
Normal file
100
packages/slate-hyperscript/test/decorations/nested.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
<highlight key="a" />This is one <highlight key="b" />block.
|
||||||
|
</paragraph>
|
||||||
|
<paragraph>
|
||||||
|
<highlight key="b" />This is block<highlight key="a" /> two.
|
||||||
|
</paragraph>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is one block.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is block two.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 12,
|
||||||
|
focusOffset: 0,
|
||||||
|
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||||
|
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchorOffset: 0,
|
||||||
|
focusOffset: 13,
|
||||||
|
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||||
|
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
100
packages/slate-hyperscript/test/decorations/overlapping.js
Normal file
100
packages/slate-hyperscript/test/decorations/overlapping.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>
|
||||||
|
<highlight key="a" />This is one <highlight key="b" />block.
|
||||||
|
</paragraph>
|
||||||
|
<paragraph>
|
||||||
|
<highlight key="a" />This is block<highlight key="b" /> two.
|
||||||
|
</paragraph>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is one block.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is block two.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 0,
|
||||||
|
focusOffset: 0,
|
||||||
|
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||||
|
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchorOffset: 12,
|
||||||
|
focusOffset: 13,
|
||||||
|
anchorKey: input.document.nodes.get(0).getFirstText().key,
|
||||||
|
focusKey: input.document.nodes.get(1).getFirstText().key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
68
packages/slate-hyperscript/test/decorations/single.js
Normal file
68
packages/slate-hyperscript/test/decorations/single.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
decorators: {
|
||||||
|
highlight: 'highlight',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a <highlight>paragraph with</highlight> a cursor position{' '}
|
||||||
|
<cursor />(closed selection).
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text:
|
||||||
|
'This is a paragraph with a cursor position (closed selection).',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectDecorations = [
|
||||||
|
{
|
||||||
|
anchorOffset: 10,
|
||||||
|
focusOffset: 24,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'highlight',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
34
packages/slate-hyperscript/test/default/single-block.js
Normal file
34
packages/slate-hyperscript/test/default/single-block.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">Single block</block>
|
||||||
|
</document>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'Single block',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
@@ -0,0 +1,112 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
A string of <mark type="bold">bold</mark> in a{' '}
|
||||||
|
<inline type="link" data={{ src: 'http://slatejs.org' }}>
|
||||||
|
Slate
|
||||||
|
</inline>{' '}
|
||||||
|
editor!
|
||||||
|
</block>
|
||||||
|
<block type="image" data={{ src: 'https://...' }} isVoid />
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'A string of ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'bold',
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'bold',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' in a ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'inline',
|
||||||
|
type: 'link',
|
||||||
|
isVoid: false,
|
||||||
|
data: {
|
||||||
|
src: 'http://slatejs.org',
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'Slate',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' editor!',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true,
|
||||||
|
data: {
|
||||||
|
src: 'https://...',
|
||||||
|
},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: '',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
@@ -1,75 +1,128 @@
|
|||||||
/** @jsx h */
|
/**
|
||||||
|
* Dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
import h from '../'
|
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import { Value, Document, Block, Text } from 'slate'
|
import fs from 'fs'
|
||||||
|
import { Value } from 'slate'
|
||||||
|
import { basename, extname, resolve } from 'path'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests.
|
||||||
|
*/
|
||||||
|
|
||||||
describe('slate-hyperscript', () => {
|
describe('slate-hyperscript', () => {
|
||||||
it('should create a document with a single block', () => {
|
describe('default settings', () => {
|
||||||
const output = (
|
const dir = resolve(__dirname, './default')
|
||||||
<document>
|
const tests = fs
|
||||||
<block type="paragraph">Single block</block>
|
.readdirSync(dir)
|
||||||
</document>
|
.filter(t => t[0] != '.')
|
||||||
)
|
.map(t => basename(t, extname(t)))
|
||||||
const expected = Document.create({
|
|
||||||
nodes: [
|
|
||||||
Block.create({
|
|
||||||
type: 'paragraph',
|
|
||||||
nodes: [Text.create('Single block')],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.deepEqual(output.toJSON(), expected.toJSON())
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const module = require(resolve(dir, test))
|
||||||
|
const { input, output } = module
|
||||||
|
|
||||||
|
const actual = input.toJSON()
|
||||||
|
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||||
|
assert.deepEqual(actual, expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should normalize a value by default', () => {
|
describe('custom tags', () => {
|
||||||
const output = (
|
const dir = resolve(__dirname, './custom')
|
||||||
<value>
|
const tests = fs
|
||||||
<document>
|
.readdirSync(dir)
|
||||||
<block type="paragraph">Valid block</block>
|
.filter(t => t[0] != '.')
|
||||||
<text>Invalid text</text>
|
.map(t => basename(t, extname(t)))
|
||||||
</document>
|
|
||||||
</value>
|
|
||||||
)
|
|
||||||
const expected = Value.create({
|
|
||||||
document: Document.create({
|
|
||||||
nodes: [
|
|
||||||
Block.create({
|
|
||||||
type: 'paragraph',
|
|
||||||
nodes: [Text.create('Valid block')],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.deepEqual(output.toJSON(), expected.toJSON())
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const module = require(resolve(dir, test))
|
||||||
|
const { input, output } = module
|
||||||
|
|
||||||
|
const actual = input.toJSON()
|
||||||
|
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||||
|
assert.deepEqual(actual, expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not normalize a value, given the option', () => {
|
describe('selections', () => {
|
||||||
const output = (
|
const dir = resolve(__dirname, './selections')
|
||||||
<value normalize={false}>
|
const tests = fs
|
||||||
<document>
|
.readdirSync(dir)
|
||||||
<block type="paragraph">Valid block</block>
|
.filter(t => t[0] != '.')
|
||||||
<text>Invalid text</text>
|
.map(t => basename(t, extname(t)))
|
||||||
</document>
|
|
||||||
</value>
|
|
||||||
)
|
|
||||||
const expected = Value.fromJSON(
|
|
||||||
{
|
|
||||||
document: Document.create({
|
|
||||||
nodes: [
|
|
||||||
Block.create({
|
|
||||||
type: 'paragraph',
|
|
||||||
nodes: [Text.create('Valid block')],
|
|
||||||
}),
|
|
||||||
Text.create('Invalid text'),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{ normalize: false }
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.deepEqual(output.toJSON(), expected.toJSON())
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const module = require(resolve(dir, test))
|
||||||
|
const { input, output, expectSelection } = module
|
||||||
|
|
||||||
|
// ensure deserialization was okay
|
||||||
|
const actual = input.toJSON()
|
||||||
|
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||||
|
assert.deepEqual(actual, expected)
|
||||||
|
|
||||||
|
// ensure expected properties of selection match
|
||||||
|
Object.keys(expectSelection).forEach(prop => {
|
||||||
|
assert.equal(input.selection[prop], expectSelection[prop])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('decorations', () => {
|
||||||
|
const dir = resolve(__dirname, './decorations')
|
||||||
|
const tests = fs
|
||||||
|
.readdirSync(dir)
|
||||||
|
.filter(t => t[0] != '.')
|
||||||
|
.map(t => basename(t, extname(t)))
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const module = require(resolve(dir, test))
|
||||||
|
const { input, output, expectDecorations } = module
|
||||||
|
|
||||||
|
// ensure deserialization was okay
|
||||||
|
const actual = input.toJSON()
|
||||||
|
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||||
|
assert.deepEqual(actual, expected)
|
||||||
|
|
||||||
|
// ensure expected properties of decorations match
|
||||||
|
// note: they are expected to match order in test result
|
||||||
|
expectDecorations.forEach((decoration, i) => {
|
||||||
|
Object.keys(decoration).forEach(prop => {
|
||||||
|
assert.deepEqual(
|
||||||
|
decoration[prop],
|
||||||
|
input.decorations.toJS()[i][prop],
|
||||||
|
`decoration ${i} had incorrect prop: ${prop}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('normalize', () => {
|
||||||
|
const dir = resolve(__dirname, './normalize')
|
||||||
|
const tests = fs
|
||||||
|
.readdirSync(dir)
|
||||||
|
.filter(t => t[0] != '.')
|
||||||
|
.map(t => basename(t, extname(t)))
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
it(test, async () => {
|
||||||
|
const module = require(resolve(dir, test))
|
||||||
|
const { input, output } = module
|
||||||
|
|
||||||
|
const actual = Value.isValue(input) ? input.toJSON() : input
|
||||||
|
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||||
|
assert.deepEqual(actual, expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
24
packages/slate-hyperscript/test/normalize/on-by-default.js
Normal file
24
packages/slate-hyperscript/test/normalize/on-by-default.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
import { Value, Document, Block, Text } from 'slate'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">Valid block</block>
|
||||||
|
<text>Invalid text</text>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = Value.create({
|
||||||
|
document: Document.create({
|
||||||
|
nodes: [
|
||||||
|
Block.create({
|
||||||
|
type: 'paragraph',
|
||||||
|
nodes: [Text.create('Valid block')],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
@@ -0,0 +1,28 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
import { Value, Document, Block, Text } from 'slate'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value normalize={false}>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">Valid block</block>
|
||||||
|
<text>Invalid text</text>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = Value.fromJSON(
|
||||||
|
{
|
||||||
|
document: Document.create({
|
||||||
|
nodes: [
|
||||||
|
Block.create({
|
||||||
|
type: 'paragraph',
|
||||||
|
nodes: [Text.create('Valid block')],
|
||||||
|
}),
|
||||||
|
Text.create('Invalid text'),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ normalize: false }
|
||||||
|
)
|
@@ -0,0 +1,50 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a paragraph with a cursor position <cursor />(closed selection).
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text:
|
||||||
|
'This is a paragraph with a cursor position (closed selection).',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectSelection = {
|
||||||
|
isCollapsed: true,
|
||||||
|
anchorOffset: 43,
|
||||||
|
focusOffset: 43,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
}
|
97
packages/slate-hyperscript/test/selections/cursor-in-mark.js
Normal file
97
packages/slate-hyperscript/test/selections/cursor-in-mark.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import { createHyperscript } from '../..'
|
||||||
|
|
||||||
|
const h = createHyperscript({
|
||||||
|
blocks: {
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
},
|
||||||
|
marks: {
|
||||||
|
b: 'bold',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<paragraph>First paragraph</paragraph>
|
||||||
|
<paragraph>
|
||||||
|
This is a paragraph with a cursor{' '}
|
||||||
|
<b>
|
||||||
|
positi<cursor />on
|
||||||
|
</b>{' '}
|
||||||
|
within a mark.
|
||||||
|
</paragraph>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'First paragraph',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is a paragraph with a cursor ',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'position',
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
object: 'mark',
|
||||||
|
type: 'bold',
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: ' within a mark.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectSelection = {
|
||||||
|
isCollapsed: true,
|
||||||
|
anchorOffset: 40,
|
||||||
|
focusOffset: 40,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is one <anchor />block.
|
||||||
|
</block>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is block<focus /> two.
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is one block.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is block two.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectSelection = {
|
||||||
|
isCollapsed: false,
|
||||||
|
anchorOffset: 12,
|
||||||
|
focusOffset: 13,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(1).key,
|
||||||
|
}
|
49
packages/slate-hyperscript/test/selections/range-in-block.js
Normal file
49
packages/slate-hyperscript/test/selections/range-in-block.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
This is a <anchor />paragraph<focus /> with an open selection.
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text: 'This is a paragraph with an open selection.',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectSelection = {
|
||||||
|
isCollapsed: false,
|
||||||
|
anchorOffset: 10,
|
||||||
|
focusOffset: 19,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
/** @jsx h */
|
||||||
|
|
||||||
|
import h from '../..'
|
||||||
|
|
||||||
|
export const input = (
|
||||||
|
<value>
|
||||||
|
<document>
|
||||||
|
<block type="paragraph">
|
||||||
|
<text>
|
||||||
|
This is{' '}
|
||||||
|
<text key="100">
|
||||||
|
a paragraph with a cursor position (closed selection).
|
||||||
|
</text>
|
||||||
|
</text>
|
||||||
|
</block>
|
||||||
|
</document>
|
||||||
|
<selection
|
||||||
|
anchorKey="100"
|
||||||
|
anchorOffset={30}
|
||||||
|
focusKey="100"
|
||||||
|
focusOffset={30}
|
||||||
|
/>
|
||||||
|
</value>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const output = {
|
||||||
|
object: 'value',
|
||||||
|
document: {
|
||||||
|
object: 'document',
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
isVoid: false,
|
||||||
|
data: {},
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
object: 'text',
|
||||||
|
leaves: [
|
||||||
|
{
|
||||||
|
object: 'leaf',
|
||||||
|
text:
|
||||||
|
'This is a paragraph with a cursor position (closed selection).',
|
||||||
|
marks: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectSelection = {
|
||||||
|
isCollapsed: true,
|
||||||
|
anchorOffset: 30,
|
||||||
|
focusOffset: 30,
|
||||||
|
anchorKey: input.texts.get(0).key,
|
||||||
|
focusKey: input.texts.get(0).key,
|
||||||
|
}
|
Reference in New Issue
Block a user