1
0
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:
jasonphillips
2018-04-27 17:50:20 -05:00
committed by Ian Storm Taylor
parent 184722bdbf
commit 0dc2a4feab
21 changed files with 1615 additions and 66 deletions

View File

@@ -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
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.
* **Decorations** can now also be specified in hyperscript, similarly to specifying a selection. See the examples under `./test/decorations/`.
---

View File

@@ -13,6 +13,42 @@ const ANCHOR = {}
const CURSOR = {}
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.
*
@@ -59,6 +95,22 @@ const CREATORS = {
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) {
return Range.create(attributes)
},
@@ -68,6 +120,8 @@ const CREATORS = {
const document = children.find(Document.isDocument)
let selection = children.find(Range.isRange) || Range.create()
const props = {}
let decorations = []
const partialDecorations = {}
// Search the document's texts to see if any of them have the anchor or
// focus information saved, so we can set the selection.
@@ -85,6 +139,44 @@ const CREATORS = {
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) {
@@ -103,7 +195,16 @@ const CREATORS = {
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
},
@@ -170,9 +271,10 @@ function createChildren(children, options = {}) {
// Create a helper to update the current node while preserving any stored
// anchor or focus information.
function setNode(next) {
const { __anchor, __focus } = node
const { __anchor, __focus, __decorations } = node
if (__anchor != null) next.__anchor = __anchor
if (__focus != null) next.__focus = __focus
if (__decorations != null) next.__decorations = __decorations
node = next
}
@@ -198,7 +300,7 @@ function createChildren(children, options = {}) {
// the existing node is empty, and the `key` option wasn't set, preserve the
// child's key when updating the node.
if (Text.isText(child)) {
const { __anchor, __focus } = child
const { __anchor, __focus, __decorations } = child
let i = node.text.length
if (!options.key && node.text.length == 0) {
@@ -214,6 +316,20 @@ function createChildren(children, options = {}) {
if (__anchor != null) node.__anchor = __anchor + 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
}
@@ -221,6 +337,13 @@ function createChildren(children, options = {}) {
// If the child is a selection object store the current position.
if (child == ANCHOR || child == CURSOR) node.__anchor = 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.
@@ -239,7 +362,7 @@ function createChildren(children, options = {}) {
*/
function resolveCreators(options) {
const { blocks = {}, inlines = {}, marks = {} } = options
const { blocks = {}, inlines = {}, marks = {}, decorators = {} } = options
const creators = {
...CREATORS,
@@ -258,6 +381,10 @@ function resolveCreators(options) {
creators[key] = normalizeMark(key, marks[key])
})
Object.keys(decorators).map(key => {
creators[key] = normalizeNode(key, decorators[key], 'decoration')
})
return creators
}

View 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: [],
},
],
},
],
},
],
},
}

View 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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: {},
},
],
},
]

View 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: [],
},
],
},
],
},
],
}

View File

@@ -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: [],
},
],
},
],
},
],
},
}

View File

@@ -1,75 +1,128 @@
/** @jsx h */
/**
* Dependencies.
*/
import h from '../'
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', () => {
it('should create a document with a single block', () => {
const output = (
<document>
<block type="paragraph">Single block</block>
</document>
)
const expected = Document.create({
nodes: [
Block.create({
type: 'paragraph',
nodes: [Text.create('Single block')],
}),
],
describe('default settings', () => {
const dir = resolve(__dirname, './default')
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 = input.toJSON()
const expected = Value.isValue(output) ? output.toJSON() : output
assert.deepEqual(actual, expected)
})
}
})
assert.deepEqual(output.toJSON(), expected.toJSON())
describe('custom tags', () => {
const dir = resolve(__dirname, './custom')
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 = input.toJSON()
const expected = Value.isValue(output) ? output.toJSON() : output
assert.deepEqual(actual, expected)
})
}
})
it('should normalize a value by default', () => {
const output = (
<value>
<document>
<block type="paragraph">Valid block</block>
<text>Invalid text</text>
</document>
</value>
)
const expected = Value.create({
document: Document.create({
nodes: [
Block.create({
type: 'paragraph',
nodes: [Text.create('Valid block')],
}),
],
}),
describe('selections', () => {
const dir = resolve(__dirname, './selections')
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, 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])
})
})
}
})
assert.deepEqual(output.toJSON(), expected.toJSON())
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}`
)
})
})
})
}
})
it('should not normalize a value, given the option', () => {
const output = (
<value normalize={false}>
<document>
<block type="paragraph">Valid block</block>
<text>Invalid text</text>
</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 }
)
describe('normalize', () => {
const dir = resolve(__dirname, './normalize')
const tests = fs
.readdirSync(dir)
.filter(t => t[0] != '.')
.map(t => basename(t, extname(t)))
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 = Value.isValue(input) ? input.toJSON() : input
const expected = Value.isValue(output) ? output.toJSON() : output
assert.deepEqual(actual, expected)
})
}
})
})

View 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')],
}),
],
}),
})

View File

@@ -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 }
)

View File

@@ -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,
}

View 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,
}

View File

@@ -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,
}

View 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,
}

View File

@@ -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,
}