substance
Version:
Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing system. It is developed to power our online editing platform [Substance](http://substance.io).
233 lines (216 loc) • 8.33 kB
JavaScript
import { test } from 'substance-test'
import { platform, setDOMSelection, EditingInterface } from 'substance'
import setupEditor from './shared/setupEditor'
import checkValues from './shared/checkValues'
import { _p1, _empty } from './fixture/samples'
function uiTest (title, fn) {
if (platform.inBrowser) {
test(title, fn)
}
}
function ffTest (title, fn) {
if (platform.isFF) {
test(title, fn)
}
}
uiTest('DOMSelection: Mapping a cursor inside a TextProperty from DOM to model', function (t) {
const { editor, surface } = setupEditor(t, _p1)
const domSelection = _getDOMSelectionFromSurface(surface)
const node = editor.el.find('[data-path="p1.content"]').getFirstChild()
setDOMSelection(node, 3)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'property',
path: ['p1', 'content'],
startOffset: 3,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
uiTest('DOMSelection: Mapping a cursor in an empty paragraph from DOM to model', function (t) {
const { editor, surface } = setupEditor(t, _empty)
const domSelection = _getDOMSelectionFromSurface(surface)
const node = editor.el.find('[data-path="empty.content"]').getFirstChild()
setDOMSelection(node, 0)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'property',
path: ['empty', 'content'],
startOffset: 0,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
uiTest('DOMSelection: Mapping a ContainerSelection from DOM to model', function (t) {
const { editor, surface } = setupEditor(t, _surfaceWithParagraphs)
const domSelection = _getDOMSelectionFromSurface(surface)
const p1Text = editor.el.find('[data-path="p1.content"]').getFirstChild()
const p2Text = editor.el.find('[data-path="p2.content"]').getFirstChild()
setDOMSelection(p1Text, 1, p2Text, 2)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'container',
startPath: ['p1', 'content'],
startOffset: 1,
endPath: ['p2', 'content'],
endOffset: 2,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
ffTest('DOMSelection: Issue #354: Wrong selection in FF when double clicking between lines', function (t) {
const { editor, surface } = setupEditor(t, _surfaceWithParagraphs)
const domSelection = _getDOMSelectionFromSurface(surface)
const surfaceEl = editor.el.find('[data-id="body"]')
setDOMSelection(surfaceEl, 0, surfaceEl, 1)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'node',
nodeId: 'p1',
mode: 'full',
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
// happens when using the same selection as in #354 in Chrome
uiTest('DOMSelection: DOM selection that starts in a TextNode and ends in a paragraph on element level', function (t) {
const { editor, surface } = setupEditor(t, _surfaceWithParagraphs)
const domSelection = _getDOMSelectionFromSurface(surface)
const p1Text = editor.el.find('[data-path="p1.content"]').getFirstChild()
const p2El = editor.el.find('[data-id="p2"]')
setDOMSelection(p1Text, 0, p2El, 0)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'container',
startPath: ['p1', 'content'],
startOffset: 0,
endPath: ['p2', 'content'],
endOffset: 0,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
uiTest('DOMSelection: Issue #376: Wrong selection mapping at end of paragraph', function (t) {
const { editor, surface } = setupEditor(t, _surfaceWithParagraphs)
const domSelection = _getDOMSelectionFromSurface(surface)
const p1span = editor.el.find('[data-id="p1"] span')
const p2El = editor.el.find('[data-id="p2"]')
setDOMSelection(p1span, 1, p2El, 0)
const sel = domSelection.getSelection()
t.notOk(!sel || sel.isNull(), 'Selection should not be null')
checkValues(t, sel.toJSON(), {
type: 'container',
startPath: ['p1', 'content'],
startOffset: 2,
endPath: ['p2', 'content'],
endOffset: 0,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
t.end()
})
uiTest('DOMSelection: Rendering a ContainerSelection', function (t) {
const { editor, doc, surface } = setupEditor(t, _surfaceWithParagraphs)
const domSelection = _getDOMSelectionFromSurface(surface)
const sel = doc.createSelection({
type: 'container',
startPath: ['p1', 'content'],
startOffset: 1,
endPath: ['p2', 'content'],
endOffset: 1,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
domSelection.setSelection(sel)
const p1Text = editor.el.find('[data-path="p1.content"]').getFirstChild()
const p2Text = editor.el.find('[data-path="p2.content"]').getFirstChild()
const wSel = window.getSelection()
t.equal(wSel.anchorNode, p1Text.getNativeElement(), 'anchorNode should be in first paragraph.')
t.equal(wSel.anchorOffset, 1, 'anchorOffset should be correct.')
t.equal(wSel.focusNode, p2Text.getNativeElement(), 'focusNode should be in second paragraph.')
t.equal(wSel.focusOffset, 1, 'focusOffset should be correct.')
t.end()
})
uiTest('DOMSelection: Rendering a cursor after inline node', function (t) {
const { editor, doc, surface } = setupEditor(t, _paragraphWithInlineNodes)
const domSelection = _getDOMSelectionFromSurface(surface)
const sel = doc.createSelection({
type: 'property',
path: ['p', 'content'],
startOffset: 3,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
const { start, end } = domSelection.mapModelToDOMCoordinates(sel)
const pSpan = editor.el.find('[data-path="p.content"]')
const third = pSpan.getChildAt(2)
t.ok(start.container === third.getNativeElement(), 'anchorNode should be correct.')
t.equal(start.offset, 0, 'anchorOffset should be correct.')
t.ok(end.container === start.container, 'focusNode should be correct.')
t.equal(end.offset, 0, 'focusOffset should be correct.')
t.end()
})
uiTest('DOMSelection: Rendering a cursor after inline node at the end of a property', function (t) {
const { editor, doc, surface } = setupEditor(t, _paragraphWithInlineNodes)
const domSelection = _getDOMSelectionFromSurface(surface)
const sel = doc.createSelection({
type: 'property',
path: ['p', 'content'],
startOffset: 13,
containerPath: ['body', 'nodes'],
surfaceId: 'body'
})
const { start, end } = domSelection.mapModelToDOMCoordinates(sel)
const pSpan = editor.el.find('[data-path="p.content"]')
t.ok(start.container === pSpan.getNativeElement(), 'anchorNode should be correct.')
t.equal(start.offset, 6, 'anchorOffset should be correct.')
t.ok(end.container === start.container, 'focusNode should be correct.')
t.equal(end.offset, 6, 'focusOffset should be correct.')
t.end()
})
function _getDOMSelectionFromSurface (surface) {
return surface.domSelection || surface.context.domSelection
}
function _surfaceWithParagraphs (doc, body) {
const tx = new EditingInterface(doc)
body.append(tx.create({
type: 'paragraph',
id: 'p1',
content: 'AA'
}))
body.append(tx.create({
type: 'paragraph',
id: 'p2',
content: 'BBB'
}))
body.append(tx.create({
type: 'paragraph',
id: 'p3',
content: 'CCCC'
}))
}
function _paragraphWithInlineNodes (doc, body) {
const tx = new EditingInterface(doc)
body.append(tx.create({
type: 'paragraph',
id: 'p',
content: '0123456789'
}))
// -> 01X234X56789X
tx.setSelection({ type: 'property', path: ['p', 'content'], startOffset: 2 })
tx.insertInlineNode({ type: 'test-inline-node', id: 'in1', content: '[1]' })
tx.setSelection({ type: 'property', path: ['p', 'content'], startOffset: 6 })
tx.insertInlineNode({ type: 'test-inline-node', id: 'in2', content: '[2]' })
tx.setSelection({ type: 'property', path: ['p', 'content'], startOffset: 12 })
tx.insertInlineNode({ type: 'test-inline-node', id: 'in3', content: '[3]' })
}