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 systems.
196 lines (171 loc) • 6.24 kB
JavaScript
/* eslint-disable no-invalid-this, indent */
import { module } from 'substance-test'
import { substanceGlobals, RenderingEngine, Component } from 'substance'
import TestComponent from './fixture/TestComponent'
import getMountPoint from './fixture/getMountPoint'
const Simple = TestComponent.Simple
const internal = RenderingEngine._internal
// runs RenderingEngine in regular mode
RenderingEngineTests()
// runs RenderingEngine in debug mode
RenderingEngineTests('debug')
function RenderingEngineTests(debug) {
const test = module('RenderingEngine' + (debug ? ' [debug-mode]' : ''), {
before: function() {
substanceGlobals.DEBUG_RENDERING = Boolean(debug)
}
})
// NOTE: this is a set of white-box tests for the internal implementation
// of ui/RenderingEngine.
// __isMapped__ is an internal state variable that is used
// by RenderingEnging to know, that a component can be reused. I.e.,
// the VirtualComponent has been mapped successfully to a Component
test('Components without refs are not mapped', function(t) {
var comp = TestComponent.create(function($$) {
return $$('div').append($$(Simple))
})
var vc = _capture(comp)
t.notOk(vc._isMapped(vc._content.children[0]), 'child element should not be mapped.')
t.end()
})
test('A component with ref is mapped', function(t) {
var comp = TestComponent.create(function($$) {
return $$('div').append($$(Simple).ref('foo'))
})
var vc = _capture(comp)
t.ok(vc._isMapped(vc._content), 'root element should be mapped')
t.ok(vc._isMapped(vc._getRef('foo')), "'foo' should be mapped")
t.end()
})
// TODO: there are more case related nesting of components
// and injecting components via props
// __isRelocated__ is an internal state variable that is used
// when a reused component has been rendered with a different parent
test('Detecting relocation when injecting a new parent element', function(t) {
function _render($$) {
var el = $$('div')
var parent = el
if (this.props.extraLayer) {
var middle = $$('div')
el.append(middle)
parent = middle
}
parent.append($$(Simple).ref('foo'))
return el
}
var comp = TestComponent.create(_render)
var vc = _capture(comp)
t.notOk(vc._isRelocated(vc._getRef('foo')), "'foo' is not relocated the first time.")
comp = TestComponent.create(_render, { extraLayer: false })
_setProps(comp, { extraLayer: true })
vc = _capture(comp)
t.ok(vc._isRelocated(vc._getRef('foo')), "'foo' is relocated the second time.")
comp = TestComponent.create(_render, { extraLayer: true })
_setProps(comp, { extraLayer: false })
vc = _capture(comp)
t.ok(vc._isRelocated(vc._getRef('foo')), "'foo' is relocated the third time.")
t.end()
})
test('Detecting relocation when injecting components (TextProperty use-case)', function(t) {
/*
This simulates a situation found often when rendering a TextProperty.
Say a text property contains an inline node.
Regularly, the content looks roughly like this:
```
AAAA<div class="inline-node">BBBB</div>CCCC
```
When selected there is a wrapper around this
```
AAAA<div class='selection'><div>BBBB</div></div>CCCC
```
Adding and removing this selection element leads to a situation
where the inline node needs to be attached to varying parent
elements.
*/
function _render($$) {
var el = $$('div')
var parent = el
el.append('AAAA')
if (this.props.extraLayer) {
var middle = $$(Simple).ref('selection')
el.append(middle)
parent = middle
}
parent.append($$(Simple).ref('foo').append('BBBB'))
el.append('CCCC')
return el
}
var comp = TestComponent.create(_render)
var vc = _capture(comp)
t.notOk(vc._isRelocated(vc._getRef('foo')), "'foo' is not relocated the first time.")
comp = TestComponent.create(_render, { extraLayer: false })
_setProps(comp, { extraLayer: true })
vc = _capture(comp)
t.ok(vc._isRelocated(vc._getRef('foo')), "'foo' is relocated the second time.")
comp = TestComponent.create(_render, { extraLayer: true })
_setProps(comp, { extraLayer: false })
vc = _capture(comp)
t.ok(vc._isRelocated(vc._getRef('foo')), "'foo' is relocated the third time.")
t.end()
})
// NOTE: Dunno if this is a good test. Obviously it depends too much on the performance of the device
test('(Performance) Rendering a large number of components with ref', (t) => {
const N = 100
const M = 52
const data = []
for (let i = 0; i < N; i++) {
let row = []
for (let j = 0; j < M; j++) {
row.push(`${i}-${j}`)
}
data.push(row)
}
class Matrix extends Component {
render($$) {
let el = $$('table').css({
overflow: 'auto',
height: '100px'
})
const data = this.props.data
for (let i = 0; i < data.length; i++) {
let row = data[i]
let rowEl = $$('tr')
for (let j = 0; j < row.length; j++) {
rowEl.append(
$$('td')
.ref(row[j])
.text(row[j]))
}
el.append(rowEl)
}
return el
}
}
let t0 = Date.now()
let comp = Matrix.mount({data}, getMountPoint(t))
let t1 = Date.now() - t0
t0 = Date.now()
comp.setProps({data})
let t2 = Date.now() - t0
t.ok(t1 < 1000, 'First rendering should be finished in reasonable time')
t.ok(t2 < 1000, 'Second rendering should be finished in reasonable time')
t.end()
})
}
function _capture(comp) {
var vc = internal._wrap(comp)
var state = new RenderingEngine.State()
internal._capture(state, vc, 'force')
vc._state = state
vc._isMapped = function(o) { return state.isMapped(o); }
vc._isRelocated = function(o) { return state.isRelocated(o); }
vc._getRef = function(ref) { return _getRef(vc, ref); }
return vc
}
function _getRef(vc, ref) {
return vc._content._context.refs[ref] || {}
}
function _setProps(comp, props) {
comp.props = props
Object.freeze(props)
}