mithril-node-render
Advanced tools
Comparing version 2.3.2 to 3.0.0
@@ -5,2 +5,3 @@ { | ||
"eslint.run": "onSave", | ||
"javascript.format.enable": false, | ||
} |
400
index.js
'use strict' | ||
const m = require('mithril/hyperscript') | ||
const Vnode = require('mithril/render/vnode') | ||
const VOID_TAGS = [ | ||
'area', | ||
'base', | ||
'br', | ||
'col', | ||
'command', | ||
'embed', | ||
'hr', | ||
'img', | ||
'input', | ||
'keygen', | ||
'link', | ||
'meta', | ||
'param', | ||
'source', | ||
'track', | ||
'wbr', | ||
'!doctype' | ||
] | ||
const VOID_TAGS = new RegExp('^(?:' + | ||
'area|' + | ||
'base|' + | ||
'br|' + | ||
'col|' + | ||
'command|' + | ||
'embed|' + | ||
'hr|' + | ||
'img|' + | ||
'input|' + | ||
'keygen|' + | ||
'link|' + | ||
'meta|' + | ||
'param|' + | ||
'source|' + | ||
'track|' + | ||
'wbr|' + | ||
'!doctype' + | ||
')$', 'i') | ||
const COMPONENT_PROPS = [ | ||
'oninit', | ||
'view', | ||
'oncreate', | ||
'onbeforeupdate', | ||
'onupdate', | ||
'onbeforeremove', | ||
'onremove' | ||
] | ||
const hasOwn = {}.hasOwnProperty | ||
function isArray (thing) { | ||
return ( | ||
thing !== '[object Array]' && | ||
Object.prototype.toString.call(thing) === '[object Array]' | ||
) | ||
function toStyleKey (str) { | ||
return str | ||
.replace(/\W+/g, '-') | ||
.replace(/([a-z\d])([A-Z])/g, '$1-$2') | ||
.toLowerCase() | ||
} | ||
function isObject (thing) { | ||
return typeof thing === 'object' | ||
function replaceHtml (m) { | ||
if (m === '&') return '&' | ||
if (m === '<') return '<' | ||
return '>' | ||
} | ||
function isFunction (thing) { | ||
return typeof thing === 'function' | ||
function replaceAttribute (m) { | ||
if (m === '&') return '&' | ||
if (m === '<') return '<' | ||
if (m === '>') return '>' | ||
return '"' | ||
} | ||
function isClassComponent (thing) { | ||
return thing.prototype != null && typeof thing.prototype.view === 'function' | ||
} | ||
const defaults = { | ||
escapeText (s) { | ||
return s.replace(/[&<>]/g, replaceHtml) | ||
}, | ||
function camelToDash (str) { | ||
return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2') | ||
} | ||
function removeEmpties (n) { | ||
return n !== '' | ||
} | ||
function omit (source, keys) { | ||
keys = keys || [] | ||
const res = Object.assign( | ||
Object.create(Object.getPrototypeOf(source)), | ||
source | ||
) | ||
keys.forEach(function (key) { | ||
if (key in res) { | ||
res[key] = null | ||
} | ||
}) | ||
return res | ||
} | ||
const copy = omit | ||
// shameless stolen from https://github.com/punkave/sanitize-html | ||
function escapeHtml (s, replaceDoubleQuote) { | ||
if (s === 'undefined') { | ||
s = '' | ||
escapeAttribute (s) { | ||
return s.replace(/[&<>"]/g, replaceAttribute) | ||
} | ||
if (typeof s !== 'string') { | ||
s = s + '' | ||
} | ||
s = s | ||
.replace(/&/g, '&') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
if (replaceDoubleQuote) { | ||
return s.replace(/"/g, '"') | ||
} | ||
return s | ||
} | ||
async function setHooks (component, vnode, hooks) { | ||
if (component.oninit) { | ||
await (component.oninit.call(vnode.state, vnode) || async function () {}) | ||
} | ||
if (component.onremove) { | ||
hooks.push(component.onremove.bind(vnode.state, vnode)) | ||
} | ||
function bindOpt (options, key) { | ||
return options[key] ? options[key].bind(options) : defaults[key] | ||
} | ||
function createAttrString (view, escapeAttributeValue) { | ||
const attrs = view.attrs | ||
if (!attrs || !Object.keys(attrs).length) { | ||
return '' | ||
} | ||
return Object.keys(attrs) | ||
.map(function (name) { | ||
const value = attrs[name] | ||
if ( | ||
name === 'key' || | ||
typeof value === 'undefined' || | ||
value === null || | ||
typeof value === 'function' | ||
) { | ||
return | ||
} | ||
if (typeof value === 'boolean') { | ||
return value ? ' ' + name : '' | ||
} | ||
if (name === 'style') { | ||
if (!value) { | ||
return | ||
} | ||
let styles = attrs.style | ||
if (isObject(styles)) { | ||
styles = Object.keys(styles) | ||
.map(function (property) { | ||
return styles[property] !== '' | ||
? [camelToDash(property).toLowerCase(), styles[property]].join( | ||
':' | ||
) | ||
: '' | ||
}) | ||
.filter(removeEmpties) | ||
.join(';') | ||
} | ||
return styles !== '' | ||
? ' style="' + escapeAttributeValue(styles, true) + '"' | ||
: '' | ||
} | ||
// Handle SVG <use> tags specially | ||
if (name === 'href' && view.tag === 'use') { | ||
return ' xlink:href="' + escapeAttributeValue(value, true) + '"' | ||
} | ||
return ( | ||
' ' + | ||
(name === 'className' ? 'class' : name) + | ||
'="' + | ||
escapeAttributeValue(value, true) + | ||
'"' | ||
) | ||
}) | ||
.join('') | ||
} | ||
async function createChildrenContent (view, options, hooks) { | ||
if (view.text != null) { | ||
return options.escapeString(view.text) | ||
} | ||
if (isArray(view.children) && !view.children.length) { | ||
return '' | ||
} | ||
return _render(view.children, options, hooks) | ||
} | ||
async function render (view, attrs, options) { | ||
options = options || {} | ||
if (view.view || isFunction(view)) { | ||
// Using a generator so I can just yield promises that need awaited. Can't just | ||
// use `async`/`await` since this is used for both sync and async renders. At | ||
// least I have generators (read: coroutines), or I'd have to implement this | ||
// using a giant pushdown automaton. :-) | ||
function * tryRender (view, attrs, options, allowAwait) { | ||
// Fast-path a very simple case. Also lets me perform some renderer | ||
// optimizations later. | ||
if (view == null) return '' | ||
if (view.view || typeof view === 'function') { | ||
// root component | ||
view = m(view, attrs) | ||
options = options || {} | ||
} else { | ||
@@ -183,106 +78,133 @@ options = attrs || {} | ||
const hooks = [] | ||
let result = '' | ||
const escapeAttribute = bindOpt(options, 'escapeAttribute') | ||
const escapeText = bindOpt(options, 'escapeText') | ||
const xml = !!options.xml | ||
const strict = xml || !!options.strict | ||
const defaultOptions = { | ||
escapeAttributeValue: escapeHtml, | ||
escapeString: escapeHtml, | ||
strict: false | ||
function write (value) { | ||
result = '' + result + value | ||
} | ||
Object.keys(defaultOptions).forEach(function (key) { | ||
if (!options.hasOwnProperty(key)) options[key] = defaultOptions[key] | ||
}) | ||
function * setHooks (source, vnode) { | ||
const promises = [] | ||
let waitFor | ||
if (allowAwait) waitFor = p => { promises.push(p) } | ||
if (source.oninit) { | ||
source.oninit.call(vnode.state, vnode, waitFor) | ||
} | ||
if (source.onremove) { | ||
hooks.push(source.onremove.bind(vnode.state, vnode)) | ||
} | ||
if (promises.length) yield promises | ||
} | ||
const result = await _render(view, options, hooks) | ||
function createAttrString (view) { | ||
for (const key in view.attrs) { | ||
if (hasOwn.call(view.attrs, key)) { | ||
let value = view.attrs[key] | ||
if (value == null || typeof value === 'function') continue | ||
const name = key === 'className' ? 'class' : key | ||
hooks.forEach(function (hook) { | ||
hook() | ||
}) | ||
if (name === 'style' && typeof value === 'object') { | ||
const styles = value | ||
const props = [] | ||
for (const key of Object.keys(styles)) { | ||
const prop = styles[key] | ||
if (prop) props.push(`${toStyleKey(key)}:${prop}`) | ||
} | ||
if (!props.length) continue | ||
value = props.join(';') | ||
} | ||
return result | ||
} | ||
if (typeof value === 'boolean') { | ||
if (xml) value = value ? 'true' : 'false' | ||
else if (!value) continue | ||
else value = '' | ||
} else { | ||
value = '' + value | ||
} | ||
async function _render (view, options, hooks) { | ||
const type = typeof view | ||
if (type === 'string') { | ||
return view | ||
write(` ${name}`) | ||
if (strict || value !== '') { | ||
write(`="${escapeAttribute(value)}"`) | ||
} | ||
} | ||
} | ||
} | ||
if (type === 'number' || type === 'boolean') { | ||
return view | ||
} | ||
function * renderComponent (vnode) { | ||
if (typeof vnode.tag !== 'function') { | ||
vnode.state = Object.create(vnode.tag) | ||
} else if (vnode.tag.prototype && vnode.tag.prototype.view) { | ||
vnode.state = new vnode.tag(vnode) | ||
} else { | ||
vnode.state = vnode.tag(vnode) | ||
} | ||
if (!view) { | ||
return '' | ||
yield * setHooks(vnode.state, vnode) | ||
if (vnode.attrs != null) yield * setHooks(vnode.attrs, vnode) | ||
vnode.instance = Vnode.normalize(vnode.state.view(vnode)) | ||
if (vnode.instance != null) yield * renderNode(vnode.instance) | ||
} | ||
if (isArray(view)) { | ||
let result = '' | ||
for (const v of view) { | ||
result += await _render(v, options, hooks) | ||
function * renderElement (vnode) { | ||
write(`<${vnode.tag}`) | ||
createAttrString(vnode) | ||
// Don't write children for void HTML elements | ||
if (!xml && VOID_TAGS.test(vnode.tag)) { | ||
write(strict ? '/>' : '>') | ||
} else { | ||
write('>') | ||
if (vnode.text != null) { | ||
const text = '' + vnode.text | ||
if (text !== '') write(escapeText(text)) | ||
} else { | ||
yield * renderChildren(vnode.children) | ||
} | ||
write(`</${vnode.tag}>`) | ||
} | ||
return result | ||
} | ||
if (view.attrs) { | ||
await setHooks(view.attrs, view, hooks) | ||
function * renderChildren (vnodes) { | ||
for (const v of vnodes) { | ||
if (v != null) yield * renderNode(v) | ||
} | ||
} | ||
// component | ||
if (view.view || view.tag) { | ||
const vnode = { children: [].concat(view.children) } | ||
let component = view.view | ||
if (isObject(view.tag)) { | ||
component = view.tag | ||
} else if (isClassComponent(view.tag)) { | ||
component = new view.tag(vnode) | ||
} else if (isFunction(view.tag)) { | ||
component = view.tag() | ||
function * renderNode (vnode) { | ||
if (vnode == null) return | ||
if (typeof vnode.tag === 'string') { | ||
vnode.state = {} | ||
if (vnode.attrs != null) yield * setHooks(vnode.attrs, vnode) | ||
switch (vnode.tag) { | ||
case '#': write(escapeText('' + vnode.children)); break | ||
case '<': write(vnode.children); break | ||
case '[': yield * renderChildren(vnode.children); break | ||
default: yield * renderElement(vnode) | ||
} | ||
} else { | ||
yield * renderComponent(vnode) | ||
} | ||
} | ||
if (component) { | ||
vnode.tag = copy(component) | ||
vnode.state = omit(component, COMPONENT_PROPS) | ||
vnode.attrs = component.attrs || view.attrs || {} | ||
yield * renderNode(Vnode.normalize(view)) | ||
for (const hook of hooks) hook() | ||
return result.concat() // hint to flatten | ||
} | ||
await setHooks(component, vnode, hooks) | ||
return _render(component.view.call(vnode.state, vnode), options, hooks) | ||
} | ||
module.exports = async (view, attrs, options) => { | ||
const iter = tryRender(view, attrs, options, true) | ||
while (true) { | ||
const {done, value} = iter.next() | ||
if (done) return value | ||
await Promise.all(value) | ||
} | ||
} | ||
if (view.tag === '<') { | ||
return '' + view.children | ||
} | ||
const children = await createChildrenContent(view, options, hooks) | ||
if (view.tag === '#') { | ||
return options.escapeString(children) | ||
} | ||
if (view.tag === '[') { | ||
return '' + children | ||
} | ||
if ( | ||
!children && | ||
(options.strict || VOID_TAGS.indexOf(view.tag.toLowerCase()) >= 0) | ||
) { | ||
return ( | ||
'<' + | ||
view.tag + | ||
createAttrString(view, options.escapeAttributeValue) + | ||
(options.strict ? '/' : '') + | ||
'>' | ||
) | ||
} | ||
return [ | ||
'<', | ||
view.tag, | ||
createAttrString(view, options.escapeAttributeValue), | ||
'>', | ||
children, | ||
'</', | ||
view.tag, | ||
'>' | ||
].join('') | ||
module.exports.sync = (view, attrs, options) => { | ||
return tryRender(view, attrs, options, false).next().value | ||
} | ||
module.exports = render | ||
module.exports.escapeHtml = escapeHtml | ||
module.exports.escapeText = defaults.escapeText | ||
module.exports.escapeAttribute = defaults.escapeAttribute |
{ | ||
"name": "mithril-node-render", | ||
"version": "2.3.2", | ||
"version": "3.0.0", | ||
"description": "Node rending of mithril views", | ||
@@ -9,6 +9,2 @@ "main": "index.js", | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/StephanHoyer/mithril-node-render.git" | ||
}, | ||
"keywords": [ | ||
@@ -22,8 +18,4 @@ "mithril", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/StephanHoyer/mithril-node-render/issues" | ||
}, | ||
"homepage": "https://github.com/StephanHoyer/mithril-node-render", | ||
"peerDependencies": { | ||
"mithril": "1.1.6" | ||
"mithril": "^2.0.4" | ||
}, | ||
@@ -37,4 +29,8 @@ "devDependencies": { | ||
"eslint-plugin-standard": "3.0.1", | ||
"mithril": "1.1.6" | ||
} | ||
"mithril": "2.0.4", | ||
"ospec": "4.0.1" | ||
}, | ||
"repository": "MithrilJS/mithril-node-render", | ||
"bugs": "https://github.com/MithrilJS/mithril-node-render/issues", | ||
"homepage": "https://github.com/MithrilJS/mithril-node-render#readme" | ||
} |
@@ -27,4 +27,6 @@ mithril-node-render | ||
```javascript | ||
// use a mock DOM so we can run mithril on the server | ||
require('mithril/test-utils/browserMock')(global) | ||
// Make Mithril happy | ||
if (!global.window) { | ||
global.window = global.document = global.requestAnimationFrame = undefined | ||
} | ||
@@ -37,2 +39,5 @@ var m = require('mithril') | ||
}) | ||
var html = render.sync(m('span', 'huhu')) | ||
// html === '<span>huhu</span>' | ||
``` | ||
@@ -43,12 +48,11 @@ | ||
As you see the rendering is asynchron. It waits for resolve of all promises | ||
that might get returned from `oninit` callbacks. | ||
As you see the rendering is asynchronous. It lets you await certain data from within `oninit` hooks. | ||
```javascript | ||
var myAsyncComponent = { | ||
oninit: function (node) { | ||
return new Promise(function (resolve) { | ||
oninit: function (node, waitFor) { | ||
waitFor(new Promise(function (resolve) { | ||
node.state.foo = 'bar' | ||
resolve() | ||
}) | ||
})) | ||
}, | ||
@@ -65,3 +69,25 @@ view: function (node) { | ||
Sync rendering | ||
-------------- | ||
You can also render synchronously. You just don't get the `waitFor` callback. | ||
```js | ||
var myAsyncComponent = { | ||
oninit: function (node, waitFor) { | ||
// waitFor === undefined | ||
new Promise(function (resolve) { | ||
node.state.foo = 'bar' | ||
resolve() | ||
}) | ||
}, | ||
view: function (node) { | ||
return m('div', node.state.foo) | ||
} | ||
} | ||
var html = render.sync(myAsyncComponent) | ||
// html === '<div>bar</div>' | ||
``` | ||
Options | ||
@@ -74,8 +100,8 @@ ------- | ||
**escapeAttributeValue(value)** | ||
`Default: render.escapeHtml` | ||
**escapeAttribute(value)** | ||
`Default: render.escapeAttribute` | ||
A filter function for attribute values. Receives value, returns what is printed. | ||
**escapeString(value)** | ||
`Default: render.escapeHtml` | ||
**escapeText(value)** | ||
`Default: render.escapeText` | ||
A filter function for string nodes. Receives value, returns what is printed. | ||
@@ -85,5 +111,9 @@ | ||
`Default: false` | ||
Set this to true to close all empty tags automatically. Default is HTML mode where tags like `<br>` and `<meta>` are allowed without closing tags. This is required if you're rendering XML or XHTML documents. | ||
Set this to true to close all empty tags automatically. Default is standard HTML mode where tags like `<br>` and `<meta>` are allowed to implicitly close themselves. This should be set to `true` if you're rendering XML-compatible HTML documents. | ||
**xml** | ||
`Default: false` | ||
Set this to true to render as generic XML instead of (possibly XML-compatible) HTML. Default is HTML mode, where children of void elements are ignored. This implies `strict: true`. | ||
See also | ||
@@ -90,0 +120,0 @@ -------- |
503
test.js
'use strict' | ||
const o = require('mithril/ospec/ospec') | ||
const m = require('mithril/render/hyperscript') | ||
const mTrust = require('mithril/render/trust') | ||
const o = require('ospec') | ||
const m = require('mithril/hyperscript') | ||
const render = require('./index') | ||
@@ -12,130 +11,170 @@ | ||
o.async = function (desc, asyncTest) { | ||
o(desc, async function (done) { | ||
await asyncTest() | ||
done() | ||
o.spec('render', () => { | ||
o('should render tag', () => { | ||
o(render.sync(m('span', 'content'))).equals('<span>content</span>') | ||
}) | ||
} | ||
o('should render classname', () => { | ||
o(render.sync(m('.foo', 'content'))).equals( | ||
'<div class="foo">content</div>' | ||
)() | ||
}) | ||
o.async = function (desc, asyncTest) { | ||
o(desc, async function (done) { | ||
await asyncTest() | ||
done() | ||
o('should render id', () => { | ||
o(render.sync(m('#bar', 'content'))).equals( | ||
'<div id="bar">content</div>' | ||
)() | ||
}) | ||
} | ||
o.async('render', async function () { | ||
o(await render(m('span', 'content'))).equals( | ||
'<span>content</span>' | ||
)('should render tag') | ||
o(await render(m('.foo', 'content'))).equals( | ||
'<div class="foo">content</div>' | ||
)('should render classname') | ||
o(await render(m('#bar', 'content'))).equals( | ||
'<div id="bar">content</div>' | ||
)('should render id') | ||
o(await render(m('br'))).equals( | ||
'<br>' | ||
)('should render short nodes when no children') | ||
o(await render(m('HR'))).equals( | ||
'<HR>' | ||
)('should render short nodes when no children and tag name is uppercase') | ||
o(await render(m('!doctype'))).equals( | ||
'<!doctype>' | ||
)('should render short node doctype') | ||
o(await render(m('!doctype', { html: true }))).equals( | ||
'<!doctype html>' | ||
)('should render short node doctype HTML5') | ||
o( | ||
await render(m('span', { 'data-foo': 'bar', selected: 'selected' })) | ||
).equals( | ||
'<span data-foo="bar" selected="selected"></span>' | ||
)('should render attributes') | ||
o(await render(m('ul', 'huhu'))).equals( | ||
'<ul>huhu</ul>' | ||
)('should render string') | ||
o(await render([m('span', 'foo'), m('div', 'bar')])).equals( | ||
'<span>foo</span><div>bar</div>' | ||
)('should render arrays') | ||
o(await render(m('div', [[m('span', 'foo'), m('div', 'bar')]]))).equals( | ||
'<div><span>foo</span><div>bar</div></div>' | ||
)('should render nested arrays') | ||
o(await render(m('span', m('div')))).equals( | ||
'<span><div></div></span>' | ||
)('should render children') | ||
o(await render(m('span', { onmousemove: function (event) {} }))).equals( | ||
'<span></span>' | ||
)('should not render events') | ||
o( | ||
await render(m('span', { style: { paddingLeft: '10px', color: 'red' } })) | ||
).equals( | ||
'<span style="padding-left:10px;color:red"></span>' | ||
)('should render children') | ||
o(await render(m('div', [1, m('span'), '2']))).equals( | ||
'<div>1<span></span>2</div>' | ||
)('should render numbers as text nodes') | ||
o(await render(m('div', 0))).equals('<div>0</div>') | ||
o(await render(m('div', false))).equals('<div></div>') | ||
o(await render(m('div', { a: true }))).equals('<div a></div>') | ||
o(await render(m('div', { a: false }))).equals('<div></div>') | ||
o(await render(m('div', { a: undefined }))).equals('<div></div>') | ||
o(await render(m('div', { key: '123', a: '123' }))).equals( | ||
'<div a="123"></div>' | ||
) | ||
o(await render(m('div', { style: null }))).equals('<div></div>') | ||
o(await render(m('div', { style: '' }))).equals('<div></div>') | ||
o(await render(m('div', { style: { color: '' } }))).equals('<div></div>') | ||
o(await render(m('div', { style: { height: '20px', color: '' } }))).equals( | ||
'<div style="height:20px"></div>' | ||
) | ||
o( | ||
await render( | ||
m('div', { style: { height: '20px', color: '', width: '10px' } }) | ||
o('should render short nodes when no children', () => { | ||
o(render.sync(m('br'))).equals( | ||
'<br>' | ||
)() | ||
}) | ||
o('should render short nodes when no children and tag name is uppercase', () => { | ||
o(render.sync(m('HR'))).equals( | ||
'<HR>' | ||
)() | ||
}) | ||
o('should render short node doctype', () => { | ||
o(render.sync(m('!doctype'))).equals( | ||
'<!doctype>' | ||
)() | ||
}) | ||
o('should render short node doctype HTML5', () => { | ||
o(render.sync(m('!doctype', { html: true }))).equals( | ||
'<!doctype html>' | ||
)() | ||
}) | ||
o('should render attributes', () => { | ||
o(render.sync(m('span', { 'data-foo': 'bar', selected: 'selected' }))).equals( | ||
'<span data-foo="bar" selected="selected"></span>' | ||
)() | ||
}) | ||
o('should render string', () => { | ||
o(render.sync(m('ul', 'huhu'))).equals( | ||
'<ul>huhu</ul>' | ||
)() | ||
}) | ||
o('should render arrays', () => { | ||
o(render.sync([m('span', 'foo'), m('div', 'bar')])).equals( | ||
'<span>foo</span><div>bar</div>' | ||
)() | ||
}) | ||
o('should render nested arrays', () => { | ||
o(render.sync(m('div', [[m('span', 'foo'), m('div', 'bar')]]))).equals( | ||
'<div><span>foo</span><div>bar</div></div>' | ||
)() | ||
}) | ||
o('should render children', () => { | ||
o(render.sync(m('span', m('div')))).equals( | ||
'<span><div></div></span>' | ||
)() | ||
}) | ||
o('should not render events', () => { | ||
o(render.sync(m('span', { onmousemove (event) {} }))).equals( | ||
'<span></span>' | ||
)() | ||
}) | ||
o('should render simple styles', () => { | ||
o( | ||
render.sync( | ||
m('div', { style: { height: '20px', color: '', width: '10px' } }) | ||
) | ||
).equals('<div style="height:20px;width:10px"></div>') | ||
}) | ||
o('should render camelcase styles', () => { | ||
o( | ||
render.sync(m('span', { style: { paddingLeft: '10px', color: 'red' } })) | ||
).equals( | ||
'<span style="padding-left:10px;color:red"></span>' | ||
) | ||
).equals('<div style="height:20px;width:10px"></div>') | ||
o(await render(m('div', { a: 'foo' }))).equals('<div a="foo"></div>') | ||
o(await render(m('div', mTrust('<foo></foo>')))).equals( | ||
'<div><foo></foo></div>' | ||
) | ||
o(await render(m('div', '<foo></foo>'))).equals( | ||
'<div><foo></foo></div>' | ||
) | ||
o(await render(m('div', { style: '"></div><div a="' }))).equals( | ||
'<div style=""></div><div a=""></div>' | ||
) | ||
o( | ||
await render(m('div', { style: '"></div><div a="' }), { | ||
escapeAttributeValue: function (value) { | ||
return value | ||
} | ||
}) | ||
).equals('<div style=""></div><div a=""></div>') | ||
o(typeof render.escapeHtml).equals('function') | ||
o(await render(m('pre', 'var = ' + JSON.stringify({ foo: 1 })))).equals( | ||
'<pre>var = {"foo":1}</pre>' | ||
) | ||
o(await render(m('svg', m('use', { href: 'fooga.com' })))).equals( | ||
'<svg><use xlink:href="fooga.com"></use></svg>' | ||
) | ||
o(await render(m('input'), { strict: true })).equals( | ||
'<input/>' | ||
)('should render closed input-tag') | ||
o(await render(m('div'), { strict: true })).equals( | ||
'<div/>' | ||
)('should render closed div-tag') | ||
}) | ||
o('should render numbers as text nodes', () => { | ||
o(render.sync(m('div', [1, m('span'), '2']))).equals( | ||
'<div>1<span></span>2</div>' | ||
) | ||
}) | ||
o('renders attributes', () => { | ||
o(render.sync(m('div', 0))).equals('<div>0</div>') | ||
o(render.sync(m('div', false))).equals('<div></div>') | ||
o(render.sync(m('div', { a: true }))).equals('<div a></div>') | ||
o(render.sync(m('div', { a: false }))).equals('<div></div>') | ||
o(render.sync(m('div', { a: undefined }))).equals('<div></div>') | ||
o(render.sync(m('div', { style: null }))).equals('<div></div>') | ||
o(render.sync(m('div', { style: '' }))).equals('<div style></div>') | ||
o(render.sync(m('div', { style: { color: '' } }))).equals('<div></div>') | ||
o(render.sync(m('div', { style: { height: '20px', color: '' } }))).equals( | ||
'<div style="height:20px"></div>' | ||
) | ||
o( | ||
render.sync( | ||
m('div', { style: { height: '20px', color: '', width: '10px' } }) | ||
) | ||
).equals('<div style="height:20px;width:10px"></div>') | ||
o(render.sync(m('div', { a: 'foo' }))).equals('<div a="foo"></div>') | ||
o(render.sync(m('div', m.trust('<foo></foo>')))).equals( | ||
'<div><foo></foo></div>' | ||
) | ||
o(render.sync(m('div', '<foo></foo>'))).equals( | ||
'<div><foo></foo></div>' | ||
) | ||
o(render.sync(m('div', { style: '"></div><div a="' }))).equals( | ||
'<div style=""></div><div a=""></div>' | ||
) | ||
o( | ||
render.sync(m('div', { style: '"></div><div a="' }), { | ||
escapeAttribute (value) { | ||
return value | ||
} | ||
}) | ||
).equals('<div style=""></div><div a=""></div>') | ||
o(render.sync(m('pre', 'var = ' + JSON.stringify({ foo: 1 })))).equals( | ||
'<pre>var = {"foo":1}</pre>' | ||
) | ||
}) | ||
o('renders svg xlink:href correctly', () => { | ||
o(render.sync(m('svg', m('use', { href: 'fooga.com' })))).equals( | ||
'<svg><use href="fooga.com"></use></svg>' | ||
) | ||
o(render.sync(m('svg', m('use', { 'xlink:href': 'fooga.com' })))).equals( | ||
'<svg><use xlink:href="fooga.com"></use></svg>' | ||
) | ||
}) | ||
o('should render closed input-tag', () => { | ||
o(render.sync(m('input'), { strict: true })).equals('<input/>') | ||
o(render.sync(m('input'), { strict: true, xml: true })).equals('<input></input>') | ||
}) | ||
o('should render closed div-tag', () => { | ||
o(render.sync(m('div'), { strict: true })).equals('<div></div>') | ||
o(render.sync(m('div'), { strict: true, xml: true })).equals('<div></div>') | ||
}) | ||
}) | ||
o.spec('components', function () { | ||
o.spec('components', () => { | ||
let myComponent, onremove | ||
o.beforeEach(function () { | ||
o.beforeEach(() => { | ||
onremove = o.spy() | ||
myComponent = { | ||
oninit: function (node) { | ||
node.state = { | ||
foo: 'bar' | ||
} | ||
oninit (node) { | ||
node.state.foo = 'bar' | ||
}, | ||
onremove: onremove, | ||
view: function (node) { | ||
onremove, | ||
view (node) { | ||
return m('div', ['hello', node.state.foo, node.attrs.foo]) | ||
@@ -146,18 +185,18 @@ } | ||
o.async('embedded', async function () { | ||
o('embedded', () => { | ||
o(onremove.callCount).equals(0) | ||
o(await render(m('div', m(myComponent)))).equals( | ||
o(render.sync(m('div', m(myComponent)))).equals( | ||
'<div><div>hellobar</div></div>' | ||
) | ||
o(onremove.callCount).equals(1) | ||
o(await render(m('span', m(myComponent, { foo: 'foz' })))).equals( | ||
o(render.sync(m('span', m(myComponent, { foo: 'foz' })))).equals( | ||
'<span><div>hellobarfoz</div></span>' | ||
) | ||
o( | ||
await render( | ||
render.sync( | ||
m( | ||
'div', | ||
m({ | ||
oninit: function () {}, | ||
view: function () { | ||
oninit () {}, | ||
view () { | ||
return m('span', 'huhu') | ||
@@ -170,7 +209,7 @@ } | ||
o( | ||
await render( | ||
render.sync( | ||
m( | ||
'div', | ||
m({ | ||
view: function () { | ||
view () { | ||
return m('span', 'huhu') | ||
@@ -184,5 +223,5 @@ } | ||
o.async('as root', async function () { | ||
o(await render(myComponent)).equals('<div>hellobar</div>') | ||
o(await render(myComponent, { foo: '-attr-foo' })).equals( | ||
o('as root', () => { | ||
o(render.sync(myComponent)).equals('<div>hellobar</div>') | ||
o(render.sync(myComponent, { foo: '-attr-foo' })).equals( | ||
'<div>hellobar-attr-foo</div>' | ||
@@ -192,5 +231,5 @@ ) | ||
o.async('with children', async function () { | ||
o('with children', () => { | ||
const parentComponent = { | ||
view: function (node) { | ||
view (node) { | ||
return m('div', node.children) | ||
@@ -200,13 +239,13 @@ } | ||
o(await render(m(parentComponent, 'howdy'))).equals('<div>howdy</div>') | ||
o(await render(m(parentComponent, m('span', 'howdy')))).equals( | ||
o(render.sync(m(parentComponent, 'howdy'))).equals('<div>howdy</div>') | ||
o(render.sync(m(parentComponent, m('span', 'howdy')))).equals( | ||
'<div><span>howdy</span></div>' | ||
) | ||
o( | ||
await render(m(parentComponent, [m('span', 'foo'), m('span', 'bar')])) | ||
render.sync(m(parentComponent, [m('span', 'foo'), m('span', 'bar')])) | ||
).equals('<div><span>foo</span><span>bar</span></div>') | ||
o( | ||
await render(m(parentComponent, m.trust('<span>trust me</span>'))) | ||
render.sync(m(parentComponent, m.trust('<span>trust me</span>'))) | ||
).equals('<div><span>trust me</span></div>') | ||
o(await render(m(parentComponent, m(myComponent, { foo: 'foz' })))).equals( | ||
o(render.sync(m(parentComponent, m(myComponent, { foo: 'foz' })))).equals( | ||
'<div><div>hellobarfoz</div></div>' | ||
@@ -216,9 +255,9 @@ ) | ||
o.async('quouting html content right', async function () { | ||
o('quouting html content right', () => { | ||
const component = { | ||
view: function (node) { | ||
view (node) { | ||
return m('span', ['huh', '> >']) | ||
} | ||
} | ||
const out = await render(component) | ||
const out = render.sync(component) | ||
o(out).equals('<span>huh> ></span>') | ||
@@ -234,10 +273,10 @@ }) | ||
for (const type in classComponents) { | ||
o.spec('component of ' + type + ' class', function () { | ||
o.spec('component of ' + type + ' class', () => { | ||
const classComponent = classComponents[type] | ||
o.async('embedded', async function () { | ||
o(await render(m('div', m(classComponent)))).equals( | ||
o('embedded', () => { | ||
o(render.sync(m('div', m(classComponent)))).equals( | ||
'<div><div>hellobar</div></div>' | ||
) | ||
o(await render(m('span', m(classComponent, { foo: 'foz' })))).equals( | ||
o(render.sync(m('span', m(classComponent, { foo: 'foz' })))).equals( | ||
'<span><div>hellobarfoz</div></span>' | ||
@@ -247,5 +286,5 @@ ) | ||
o.async('as root', async function () { | ||
o(await render(classComponent)).equals('<div>hellobar</div>') | ||
o(await render(classComponent, { foo: '-attr-foo' })).equals( | ||
o('as root', () => { | ||
o(render.sync(classComponent)).equals('<div>hellobar</div>') | ||
o(render.sync(classComponent, { foo: '-attr-foo' })).equals( | ||
'<div>hellobar-attr-foo</div>' | ||
@@ -257,6 +296,6 @@ ) | ||
o.async('`this` in component', async function () { | ||
o('`this` in component', () => { | ||
const oninit = o.spy() | ||
const myComponent = { | ||
oninit: function (vnode) { | ||
oninit (vnode) { | ||
oninit() | ||
@@ -272,3 +311,3 @@ o(this).equals(vnode.state)( | ||
}, | ||
view: function (vnode) { | ||
view (vnode) { | ||
o(this).equals(vnode.state)( | ||
@@ -279,3 +318,3 @@ 'vnode.state should be the context in the view' | ||
}, | ||
onremove: function (vnode) { | ||
onremove (vnode) { | ||
o(this).equals(vnode.state)( | ||
@@ -288,3 +327,3 @@ 'vnode.state should be the context in `onremove`' | ||
o(await render([m(myComponent), m(myComponent)])).equals( | ||
o(render.sync([m(myComponent), m(myComponent)])).equals( | ||
'<div>hello</div><div>hello</div>' | ||
@@ -298,7 +337,7 @@ ) | ||
o.async('lifecycle hooks as attributes on elements', async function () { | ||
o('lifecycle hooks as attributes on elements', () => { | ||
let initialized, removed | ||
await render( | ||
render.sync( | ||
m('p', { | ||
oninit: function (vnode) { | ||
oninit (vnode) { | ||
initialized = true | ||
@@ -309,3 +348,3 @@ o(this).equals(vnode.state)( | ||
}, | ||
onremove: function (vnode) { | ||
onremove (vnode) { | ||
removed = true | ||
@@ -322,17 +361,20 @@ o(this).equals(vnode.state)( | ||
o.async('lifecycle hooks as attributes on components', async function () { | ||
let attrInitialized, attrRemoved, tagInitialized, tagRemoved | ||
o('lifecycle hooks as attributes on components', () => { | ||
let attrInitialized = false | ||
let attrRemoved = false | ||
let tagInitialized = false | ||
let tagRemoved = false | ||
const myComponent = { | ||
oninit: function () { | ||
o(attrInitialized).equals(true)( | ||
'`attr.oninit()` should run before `tag.oninit()`' | ||
oninit () { | ||
o(attrInitialized).equals(false)( | ||
'`attr.oninit()` should run after `tag.oninit()`' | ||
) | ||
tagInitialized = true | ||
}, | ||
view: function () { | ||
view () { | ||
return m('p', 'p') | ||
}, | ||
onremove: function () { | ||
o(attrRemoved).equals(true)( | ||
'`attr.onremove()` should run before `tag.onremove()`' | ||
onremove () { | ||
o(attrRemoved).equals(false)( | ||
'`attr.onremove()` should run after `tag.onremove()`' | ||
) | ||
@@ -343,5 +385,5 @@ tagRemoved = true | ||
o( | ||
await render( | ||
render.sync( | ||
m(myComponent, { | ||
oninit: function (vnode) { | ||
oninit (vnode) { | ||
o(this).equals(vnode.state)( | ||
@@ -351,4 +393,7 @@ 'vnode.state should be the context in `attr.oninit`' | ||
attrInitialized = true | ||
o(tagInitialized).equals(true)( | ||
'`attr.oninit()` should run after `tag.oninit()`' | ||
) | ||
}, | ||
onremove: function (vnode) { | ||
onremove (vnode) { | ||
o(this).equals(vnode.state)( | ||
@@ -358,2 +403,5 @@ 'vnode.state should be the context in `attr.onremove`' | ||
attrRemoved = true | ||
o(tagRemoved).equals(true)( | ||
'`attr.onremove()` should run after `tag.onremove()`' | ||
) | ||
} | ||
@@ -367,3 +415,3 @@ }) | ||
o.async('lifecycle hooks of class component', async function () { | ||
o('lifecycle hooks of class component', () => { | ||
let initialized, removed | ||
@@ -394,3 +442,3 @@ const classComponent = class { | ||
} | ||
o(await render(m(classComponent))).equals('<p>hello</p>') | ||
o(render.sync(m(classComponent))).equals('<p>hello</p>') | ||
o(initialized).equals(true)('classComponent#oninit should run') | ||
@@ -400,5 +448,5 @@ o(removed).equals(true)('classComponent#onremove should run') | ||
o.async( | ||
o( | ||
'onremove hooks should be called once the whole tree has been inititalized', | ||
async function () { | ||
() => { | ||
let initialized = 0 | ||
@@ -410,11 +458,11 @@ const onremove = o.spy() | ||
} | ||
const attrs = { oninit: oninit, onremove: onremove } | ||
const attrs = { oninit, onremove } | ||
const myComponent = { | ||
oninit: oninit, | ||
view: function () { | ||
oninit, | ||
view () { | ||
return m('p', attrs, 'p') | ||
}, | ||
onremove: onremove | ||
onremove | ||
} | ||
o(await render([m(myComponent, attrs), m(myComponent, attrs)])) | ||
render.sync([m(myComponent, attrs), m(myComponent, attrs)]) | ||
@@ -431,3 +479,3 @@ /* | ||
o.async('hooks are called top-down, depth-first on elements', async function () { | ||
o('hooks are called top-down, depth-first on elements', () => { | ||
/* | ||
@@ -450,7 +498,7 @@ Suppose a tree with the following structure: two levels of depth, | ||
let ulRemoved = false | ||
const html = await render([ | ||
const html = render.sync([ | ||
m( | ||
'p', | ||
{ | ||
oninit: function () { | ||
oninit () { | ||
pInit = true | ||
@@ -460,3 +508,3 @@ o(aInit).equals(false) | ||
}, | ||
onremove: function () { | ||
onremove () { | ||
pRemoved = true | ||
@@ -470,3 +518,3 @@ o(aRemoved).equals(false) | ||
{ | ||
oninit: function () { | ||
oninit () { | ||
aInit = true | ||
@@ -476,3 +524,3 @@ o(pInit).equals(true) | ||
}, | ||
onremove: function () { | ||
onremove () { | ||
aRemoved = true | ||
@@ -489,3 +537,3 @@ o(pRemoved).equals(true) | ||
{ | ||
oninit: function () { | ||
oninit () { | ||
ulInit = true | ||
@@ -495,3 +543,3 @@ o(pInit).equals(true) | ||
}, | ||
onremove: function () { | ||
onremove () { | ||
ulRemoved = true | ||
@@ -509,30 +557,74 @@ o(pRemoved).equals(true) | ||
o.spec('async', function () { | ||
let myAsyncComponent | ||
o.beforeEach(function () { | ||
myAsyncComponent = { | ||
oninit: function (node) { | ||
return new Promise(function (resolve) { | ||
node.state.foo = 'bar' | ||
setTimeout(resolve, 10) | ||
}) | ||
o.spec('async', () => { | ||
o('render object components', async () => { | ||
const oninitSpy = o.spy() | ||
const viewSpy = o.spy() | ||
const myAsyncComponent = { | ||
oninit (vnode, waitFor) { | ||
this.foo = 'bar' | ||
oninitSpy() | ||
waitFor(Promise.resolve().then(() => { this.foo = 'baz' })) | ||
}, | ||
view: function (node) { | ||
return m('div', node.state.foo) | ||
view (vnode) { | ||
viewSpy() | ||
return m('div', this.foo) | ||
} | ||
} | ||
const p = render(myAsyncComponent) | ||
o(oninitSpy.callCount).equals(1) | ||
o(viewSpy.callCount).equals(0) | ||
const html = await p | ||
o(html).equals('<div>baz</div>') | ||
o(oninitSpy.callCount).equals(1) | ||
o(viewSpy.callCount).equals(1) | ||
}) | ||
o.async('render components', async function () { | ||
const html = await render(myAsyncComponent) | ||
o('render nodes', async () => { | ||
const oninitSpy = o.spy() | ||
const html = await render( | ||
m( | ||
'span', | ||
{ | ||
oninit (node, waitFor) { | ||
waitFor(new Promise(resolve => { | ||
oninitSpy() | ||
setTimeout(resolve, 10) | ||
})) | ||
} | ||
}, | ||
'foo' | ||
) | ||
) | ||
o(html).equals('<span>foo</span>') | ||
o(oninitSpy.callCount).equals(1) | ||
}) | ||
o('render object components sync', () => { | ||
const waitFors = [] | ||
const myAsyncComponent = { | ||
oninit (vnode, waitFor) { | ||
waitFors.push(waitFor) | ||
this.foo = 'bar' | ||
return Promise.resolve().then(() => { this.foo = 'baz' }) | ||
}, | ||
view (vnode) { | ||
return m('div', this.foo) | ||
} | ||
} | ||
const html = render.sync(myAsyncComponent) | ||
o(waitFors).deepEquals([undefined]) | ||
o(html).equals('<div>bar</div>') | ||
}) | ||
o.async('render nodes', async function () { | ||
o('render nodes sync', () => { | ||
const waitFors = [] | ||
const oninitSpy = o.spy() | ||
const html = await render( | ||
const html = render.sync( | ||
m( | ||
'span', | ||
{ | ||
oninit: function (node) { | ||
oninit (node, waitFor) { | ||
waitFors.push(waitFor) | ||
return new Promise(resolve => { | ||
@@ -547,2 +639,3 @@ oninitSpy() | ||
) | ||
o(waitFors).deepEquals([undefined]) | ||
o(html).equals('<span>foo</span>') | ||
@@ -553,6 +646,6 @@ o(oninitSpy.callCount).equals(1) | ||
o.async('render closure components', async function () { | ||
const closureComponent = function () { | ||
o('render closure components', () => { | ||
const closureComponent = () => { | ||
return { | ||
view: function (node) { | ||
view (node) { | ||
return m('p', 'p') | ||
@@ -562,5 +655,5 @@ } | ||
} | ||
o(await render(closureComponent())).equals('<p>p</p>') | ||
o(render.sync(closureComponent())).equals('<p>p</p>') | ||
}) | ||
o.run() |
@@ -1,9 +0,16 @@ | ||
"use strict"; | ||
const m = require('mithril/hyperscript') | ||
'use strict'; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var BabelClassComponent = function () { | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
var m = require('mithril/hyperscript'); | ||
var BabelClassComponent = | ||
/*#__PURE__*/ | ||
function () { | ||
function BabelClassComponent(vnode) { | ||
@@ -16,3 +23,3 @@ _classCallCheck(this, BabelClassComponent); | ||
_createClass(BabelClassComponent, [{ | ||
key: 'oninit', | ||
key: "oninit", | ||
value: function oninit() { | ||
@@ -22,3 +29,3 @@ this.vnode.state.foo = 'bar'; | ||
}, { | ||
key: 'view', | ||
key: "view", | ||
value: function view() { | ||
@@ -25,0 +32,0 @@ return m('div', ['hello', this.vnode.state.foo, this.vnode.attrs.foo]); |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict' | ||
const m = require('mithril/hyperscript') | ||
@@ -6,14 +6,14 @@ | ||
constructor (vnode) { | ||
this.vnode = vnode; | ||
this.vnode = vnode | ||
} | ||
oninit() { | ||
this.vnode.state.foo = 'bar'; | ||
oninit () { | ||
this.vnode.state.foo = 'bar' | ||
} | ||
view() { | ||
return m('div', ['hello', this.vnode.state.foo, this.vnode.attrs.foo]); | ||
view () { | ||
return m('div', ['hello', this.vnode.state.foo, this.vnode.attrs.foo]) | ||
} | ||
} | ||
module.exports = ES6ClassComponent; | ||
module.exports = ES6ClassComponent |
@@ -5,14 +5,14 @@ 'use strict' | ||
function ClassComponent(vnode) { | ||
function ClassComponent (vnode) { | ||
this.vnode = vnode | ||
} | ||
ClassComponent.prototype.oninit = function oninit() { | ||
ClassComponent.prototype.oninit = function oninit () { | ||
this.vnode.state.foo = 'bar' | ||
} | ||
ClassComponent.prototype.view = function view() { | ||
ClassComponent.prototype.view = function view () { | ||
return m('div', ['hello', this.vnode.state.foo, this.vnode.attrs.foo]) | ||
} | ||
module.exports = ClassComponent; | ||
module.exports = ClassComponent |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30798
14
838
118
8
1
1
1