diff --git a/.eslintrc b/.eslintrc
index b737d1bcf..7947fc265 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -14,6 +14,7 @@
},
"env": {
"browser": true,
+ "mocha": true,
"node": true
},
"rules": {
diff --git a/lib/components/leaf.js b/lib/components/leaf.js
index e4491633c..8585c767c 100644
--- a/lib/components/leaf.js
+++ b/lib/components/leaf.js
@@ -72,7 +72,6 @@ class Leaf extends React.Component {
const native = window.getSelection()
const el = ReactDOM.findDOMNode(this).firstChild
-
// If both the start and end are here, set the selection all at once.
if (hasStart && hasEnd) {
native.removeAllRanges()
diff --git a/lib/models/state.js b/lib/models/state.js
index 064ed9fe4..121abf3f1 100644
--- a/lib/models/state.js
+++ b/lib/models/state.js
@@ -858,10 +858,19 @@ class State extends new Record(DEFAULTS) {
document = document.wrapInlineAtRange(selection, type, data)
// Determine what the selection should be after wrapping.
- if (selection.isExpanded) {
- const start = document.getNextText(selection.startKey)
- const end = document.getPreviousText(selection.endKey)
- selection = selection.moveToRangeOf(start, end)
+ if (selection.isCollapsed) {
+ selection = selection
+ }
+
+ else if (selection.startOffset == 0) {
+ const text = document.getDescendant(selection.startKey)
+ selection = selection.moveToRangeOf(text)
+ selection = selection.normalize(document)
+ }
+
+ else {
+ const text = document.getNextText(selection.startKey)
+ selection = selection.moveToRangeOf(text)
selection = selection.normalize(document)
}
diff --git a/test/rendering/fixtures/custom-block/index.js b/test/rendering/fixtures/custom-block/index.js
new file mode 100644
index 000000000..7cb16796d
--- /dev/null
+++ b/test/rendering/fixtures/custom-block/index.js
@@ -0,0 +1,10 @@
+
+import React from 'react'
+
+function Code(props) {
+ return
{props.children}
+}
+
+export function renderNode(node) {
+ if (node.type == 'code') return Code
+}
diff --git a/test/rendering/fixtures/custom-block/input.yaml b/test/rendering/fixtures/custom-block/input.yaml
new file mode 100644
index 000000000..ab7f93d42
--- /dev/null
+++ b/test/rendering/fixtures/custom-block/input.yaml
@@ -0,0 +1,14 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
+ - kind: block
+ type: code
+ nodes:
+ - kind: text
+ ranges:
+ - text: another
diff --git a/test/rendering/fixtures/custom-block/output.html b/test/rendering/fixtures/custom-block/output.html
new file mode 100644
index 000000000..42a82304a
--- /dev/null
+++ b/test/rendering/fixtures/custom-block/output.html
@@ -0,0 +1,15 @@
+
+
+
+
+ word
+
+
+
+
+
+ another
+
+
+
+
diff --git a/test/rendering/fixtures/custom-inline/index.js b/test/rendering/fixtures/custom-inline/index.js
new file mode 100644
index 000000000..da3646fee
--- /dev/null
+++ b/test/rendering/fixtures/custom-inline/index.js
@@ -0,0 +1,11 @@
+
+import React from 'react'
+
+function Link(props) {
+ const href = props.node.data.get('href')
+ return {props.children}
+}
+
+export function renderNode(node) {
+ if (node.type == 'link') return Link
+}
diff --git a/test/rendering/fixtures/custom-inline/input.yaml b/test/rendering/fixtures/custom-inline/input.yaml
new file mode 100644
index 000000000..bb0a47aed
--- /dev/null
+++ b/test/rendering/fixtures/custom-inline/input.yaml
@@ -0,0 +1,13 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: inline
+ type: link
+ data:
+ href: https://google.com
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
diff --git a/test/rendering/fixtures/custom-inline/output.html b/test/rendering/fixtures/custom-inline/output.html
new file mode 100644
index 000000000..b4a008d65
--- /dev/null
+++ b/test/rendering/fixtures/custom-inline/output.html
@@ -0,0 +1,10 @@
+
+
diff --git a/test/rendering/fixtures/default-block-and-inline/index.js b/test/rendering/fixtures/default-block-and-inline/index.js
new file mode 100644
index 000000000..dbf0139ef
--- /dev/null
+++ b/test/rendering/fixtures/default-block-and-inline/index.js
@@ -0,0 +1,4 @@
+
+/**
+ * Nothing, pure defaults.
+ */
diff --git a/test/rendering/fixtures/default-block-and-inline/input.yaml b/test/rendering/fixtures/default-block-and-inline/input.yaml
new file mode 100644
index 000000000..353b1de1f
--- /dev/null
+++ b/test/rendering/fixtures/default-block-and-inline/input.yaml
@@ -0,0 +1,11 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: inline
+ type: default
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
diff --git a/test/rendering/fixtures/default-block-and-inline/output.html b/test/rendering/fixtures/default-block-and-inline/output.html
new file mode 100644
index 000000000..0544dc005
--- /dev/null
+++ b/test/rendering/fixtures/default-block-and-inline/output.html
@@ -0,0 +1,10 @@
+
+
diff --git a/test/rendering/fixtures/default-block/index.js b/test/rendering/fixtures/default-block/index.js
new file mode 100644
index 000000000..dbf0139ef
--- /dev/null
+++ b/test/rendering/fixtures/default-block/index.js
@@ -0,0 +1,4 @@
+
+/**
+ * Nothing, pure defaults.
+ */
diff --git a/test/rendering/fixtures/default-block/input.yaml b/test/rendering/fixtures/default-block/input.yaml
new file mode 100644
index 000000000..9c6aefff6
--- /dev/null
+++ b/test/rendering/fixtures/default-block/input.yaml
@@ -0,0 +1,8 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
diff --git a/test/rendering/fixtures/default-block/output.html b/test/rendering/fixtures/default-block/output.html
new file mode 100644
index 000000000..94d33447a
--- /dev/null
+++ b/test/rendering/fixtures/default-block/output.html
@@ -0,0 +1,8 @@
+
+
diff --git a/test/rendering/fixtures/multiple-custom-block/index.js b/test/rendering/fixtures/multiple-custom-block/index.js
new file mode 100644
index 000000000..7cb16796d
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-block/index.js
@@ -0,0 +1,10 @@
+
+import React from 'react'
+
+function Code(props) {
+ return {props.children}
+}
+
+export function renderNode(node) {
+ if (node.type == 'code') return Code
+}
diff --git a/test/rendering/fixtures/multiple-custom-block/input.yaml b/test/rendering/fixtures/multiple-custom-block/input.yaml
new file mode 100644
index 000000000..92c5c3fbb
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-block/input.yaml
@@ -0,0 +1,20 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
+ - kind: block
+ type: code
+ nodes:
+ - kind: text
+ ranges:
+ - text: another
+ - kind: block
+ type: code
+ nodes:
+ - kind: text
+ ranges:
+ - text: third
diff --git a/test/rendering/fixtures/multiple-custom-block/output.html b/test/rendering/fixtures/multiple-custom-block/output.html
new file mode 100644
index 000000000..a3bb0eda5
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-block/output.html
@@ -0,0 +1,22 @@
+
+
+
+
+ word
+
+
+
+
+
+ another
+
+
+
+
+
+
+ third
+
+
+
+
diff --git a/test/rendering/fixtures/multiple-custom-inline/index.js b/test/rendering/fixtures/multiple-custom-inline/index.js
new file mode 100644
index 000000000..da3646fee
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-inline/index.js
@@ -0,0 +1,11 @@
+
+import React from 'react'
+
+function Link(props) {
+ const href = props.node.data.get('href')
+ return {props.children}
+}
+
+export function renderNode(node) {
+ if (node.type == 'link') return Link
+}
diff --git a/test/rendering/fixtures/multiple-custom-inline/input.yaml b/test/rendering/fixtures/multiple-custom-inline/input.yaml
new file mode 100644
index 000000000..8bdbdc47a
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-inline/input.yaml
@@ -0,0 +1,21 @@
+
+nodes:
+ - kind: block
+ type: default
+ nodes:
+ - kind: inline
+ type: link
+ data:
+ href: https://google.com
+ nodes:
+ - kind: text
+ ranges:
+ - text: another
+ - kind: inline
+ type: link
+ data:
+ href: https://google.com
+ nodes:
+ - kind: text
+ ranges:
+ - text: word
diff --git a/test/rendering/fixtures/multiple-custom-inline/output.html b/test/rendering/fixtures/multiple-custom-inline/output.html
new file mode 100644
index 000000000..6ed4e2bd9
--- /dev/null
+++ b/test/rendering/fixtures/multiple-custom-inline/output.html
@@ -0,0 +1,15 @@
+
+
diff --git a/test/rendering/index.js b/test/rendering/index.js
new file mode 100644
index 000000000..8189e3a6b
--- /dev/null
+++ b/test/rendering/index.js
@@ -0,0 +1,59 @@
+
+import React from 'react'
+import ReactDOM from 'react-dom/server'
+import assert from 'assert'
+import cheerio from 'cheerio'
+import fs from 'fs'
+import readMetadata from 'read-metadata'
+import { Editor, Raw } from '../..'
+import { resolve } from 'path'
+
+/**
+ * Tests.
+ */
+
+describe('rendering', () => {
+ const tests = fs.readdirSync(resolve(__dirname, './fixtures'))
+
+ for (const test of tests) {
+ it(test, () => {
+ const dir = resolve(__dirname, './fixtures', test)
+ const input = readMetadata.sync(resolve(dir, 'input.yaml'))
+ const output = fs.readFileSync(resolve(dir, 'output.html'), 'utf8')
+ const props = {
+ state: Raw.deserialize(input),
+ onChange: () => {},
+ ...require(dir)
+ }
+
+ const string = ReactDOM.renderToStaticMarkup()
+ const expected = cheerio
+ .load(output)
+ .html()
+ .trim()
+ .replace(/\n/gm, '')
+ .replace(/>\s*<')
+
+ assert.equal(clean(string), expected)
+ })
+ }
+})
+
+/**
+ * Clean a renderer `html` string, removing dynamic attributes.
+ *
+ * @param {String} html
+ * @return {String}
+ */
+
+function clean(html) {
+ const $ = cheerio.load(html)
+
+ $('*').each((i, el) => {
+ $(el).removeAttr('data-key')
+ $(el).removeAttr('data-offset-key')
+ $(el).removeAttr('style')
+ })
+
+ return $.html()
+}
diff --git a/test/server.js b/test/server.js
index f36494c7b..6072ef27d 100644
--- a/test/server.js
+++ b/test/server.js
@@ -1,2 +1,3 @@
+import './rendering'
import './transforms'
diff --git a/test/transforms/index.js b/test/transforms/index.js
index 65a63f760..a301afb4a 100644
--- a/test/transforms/index.js
+++ b/test/transforms/index.js
@@ -16,7 +16,7 @@ describe('transforms', () => {
const transforms = fs.readdirSync(dir)
for (const transform of transforms) {
- describe(toCamel(transform), () => {
+ describe(`${toCamel(transform)}()`, () => {
const dir = resolve(__dirname, './fixtures', transform)
const tests = fs.readdirSync(dir)