├── .gitignore ├── .travis.yml ├── README.md ├── elements.js ├── index.js ├── init.js ├── modules ├── attributes.js ├── class.js ├── dataset.js ├── index.js ├── props.js └── style.js ├── package-lock.json ├── package.json ├── test └── index.js └── type-definitions └── snabbdom-to-html.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '6' 5 | - '5' 6 | - '4' 7 | script: npm run test 8 | branches: 9 | only: 10 | - master -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snabbdom to HTML 2 | 3 | Render [Snabbdom](https://github.com/paldepind/snabbdom) Vnode’s to HTML strings 4 | 5 | ## Install 6 | 7 | With [`npm`](https://www.npmjs.com/) do: 8 | 9 | ```bash 10 | npm install snabbdom-to-html 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | var h = require('snabbdom/h') 17 | var toHTML = require('snabbdom-to-html') 18 | 19 | var output = toHTML( 20 | h('div', { style: { color: 'red' } }, 'The quick brown fox jumps') 21 | ) 22 | 23 | console.log(output) 24 | // =>
The quick brown fox jumps
25 | ``` 26 | 27 | ### Advanced usage 28 | 29 | This library is built replicating the modular approach used in Snabbdom. So you can do the following if you need to implement any custom functionality. 30 | 31 | ```js 32 | var h = require('snabbdom/h') 33 | 34 | var init = require('snabbdom-to-html/init') 35 | var modules = require('snabbdom-to-html/modules') 36 | var toHTML = init([ 37 | modules.class, 38 | modules.props, 39 | modules.attributes, 40 | modules.style 41 | ]) 42 | 43 | var output = toHTML( 44 | h('div', { style: { color: 'lime' } }, 'over the lazy fox') 45 | ) 46 | 47 | console.log(output) 48 | // =>
over the lazy fox
49 | ``` 50 | 51 | The `init` function accepts an array of functions (modules). Modules have the following signature: `(vnode, attributes) => undefined`, where `attributes` is an [ES2015 Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instance. 52 | 53 | You can do `attributes.set(key, value)`, `attributes.get(key)` and `attributes.delete(key)` and so on. You can check out the built-in modules to get the idea. 54 | 55 | The built-in modules are available from `snabbdom-to-html/modules`, and these are: 56 | 57 | - `attributes` 58 | - `class` 59 | - `props` 60 | - `style` 61 | 62 | ## Support 63 | 64 | This is tested against Node.js 4.x and up. If you need to run this in the browser you might need to include something like [`es6-shim`](https://github.com/paulmillr/es6-shim) to ensure `Map` support. 65 | 66 | ## License 67 | 68 | MIT 69 | -------------------------------------------------------------------------------- /elements.js: -------------------------------------------------------------------------------- 1 | 2 | // All SVG children elements, not in this list, should self-close 3 | 4 | exports.CONTAINER = { 5 | // http://www.w3.org/TR/SVG/intro.html#TermContainerElement 6 | 'a': true, 7 | 'defs': true, 8 | 'glyph': true, 9 | 'g': true, 10 | 'marker': true, 11 | 'mask': true, 12 | 'missing-glyph': true, 13 | 'pattern': true, 14 | 'svg': true, 15 | 'switch': true, 16 | 'symbol': true, 17 | 'text': true, 18 | 'clipPath': true, 19 | 'linearGradient': true, 20 | 21 | 'style': true, 22 | 'script': true, 23 | 24 | // http://www.w3.org/TR/SVG/intro.html#TermDescriptiveElement 25 | 'desc': true, 26 | 'metadata': true, 27 | 'title': true 28 | } 29 | 30 | // http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements 31 | 32 | exports.VOID = { 33 | area: true, 34 | base: true, 35 | br: true, 36 | col: true, 37 | embed: true, 38 | hr: true, 39 | img: true, 40 | input: true, 41 | keygen: true, 42 | link: true, 43 | meta: true, 44 | param: true, 45 | source: true, 46 | track: true, 47 | wbr: true 48 | } 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var init = require('./init') 3 | var modules = require('./modules') 4 | 5 | var toHTML = init([ 6 | modules.attributes, 7 | modules.props, 8 | modules.class, 9 | modules.style, 10 | modules.dataset 11 | ]) 12 | 13 | module.exports = toHTML 14 | -------------------------------------------------------------------------------- /init.js: -------------------------------------------------------------------------------- 1 | 2 | var escape = require('lodash.escape') 3 | var parseSelector = require('parse-sel') 4 | var VOID_ELEMENTS = require('./elements').VOID 5 | var CONTAINER_ELEMENTS = require('./elements').CONTAINER 6 | 7 | module.exports = function init (modules) { 8 | function parse (vnode, node) { 9 | var result = [] 10 | var attributes = new Map([ 11 | // These can be overwritten because that’s what happens in snabbdom 12 | ['id', node.id], 13 | ['class', node.className] 14 | ]) 15 | 16 | modules.forEach(function (fn, index) { 17 | fn(vnode, attributes) 18 | }) 19 | attributes.forEach(function (value, key) { 20 | if (value && value !== '') { 21 | result.push(key + '="' + value + '"') 22 | } 23 | }) 24 | 25 | return result.join(' ') 26 | } 27 | 28 | return function renderToString (vnode) { 29 | if (typeof vnode === 'undefined' || vnode === null) { 30 | return '' 31 | } 32 | 33 | if (!vnode.sel && typeof vnode.text === 'string') { 34 | return escape(vnode.text) 35 | } 36 | 37 | vnode.data = vnode.data || {} 38 | 39 | // Support thunks 40 | if (vnode.data.hook && 41 | typeof vnode.data.hook.init === 'function' && 42 | typeof vnode.data.fn === 'function') { 43 | vnode.data.hook.init(vnode) 44 | } 45 | 46 | var node = parseSelector(vnode.sel) 47 | var tagName = node.tagName 48 | var attributes = parse(vnode, node) 49 | var svg = vnode.data.ns === 'http://www.w3.org/2000/svg' 50 | var tag = [] 51 | 52 | if (tagName === '!') { 53 | return '' 54 | } 55 | 56 | // Open tag 57 | tag.push('<' + tagName) 58 | if (attributes.length) { 59 | tag.push(' ' + attributes) 60 | } 61 | if (svg && CONTAINER_ELEMENTS[tagName] !== true) { 62 | tag.push(' /') 63 | } 64 | tag.push('>') 65 | 66 | // Close tag, if needed 67 | if ((VOID_ELEMENTS[tagName] !== true && !svg) || 68 | (svg && CONTAINER_ELEMENTS[tagName] === true)) { 69 | if (vnode.data.props && vnode.data.props.innerHTML) { 70 | tag.push(vnode.data.props.innerHTML) 71 | } else if (vnode.text) { 72 | tag.push(escape(vnode.text)) 73 | } else if (vnode.children) { 74 | vnode.children.forEach(function (child) { 75 | tag.push(renderToString(child)) 76 | }) 77 | } 78 | tag.push('') 79 | } 80 | 81 | return tag.join('') 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/attributes.js: -------------------------------------------------------------------------------- 1 | 2 | var forOwn = require('lodash.forown') 3 | var escape = require('lodash.escape') 4 | 5 | // data.attrs 6 | 7 | module.exports = function attrsModule (vnode, attributes) { 8 | var attrs = vnode.data.attrs || {} 9 | 10 | forOwn(attrs, function (value, key) { 11 | attributes.set(key, escape(value)) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /modules/class.js: -------------------------------------------------------------------------------- 1 | 2 | var forOwn = require('lodash.forown') 3 | var remove = require('lodash.remove') 4 | var uniq = require('lodash.uniq') 5 | 6 | // data.class 7 | 8 | module.exports = function classModule (vnode, attributes) { 9 | var values 10 | var _add = [] 11 | var _remove = [] 12 | var classes = vnode.data.class || {} 13 | var existing = attributes.get('class') 14 | existing = existing.length > 0 ? existing.split(' ') : [] 15 | 16 | forOwn(classes, function (value, key) { 17 | if (value) { 18 | _add.push(key) 19 | } else { 20 | _remove.push(key) 21 | } 22 | }) 23 | 24 | values = remove(uniq(existing.concat(_add)), function (value) { 25 | return _remove.indexOf(value) < 0 26 | }) 27 | 28 | if (values.length) { 29 | attributes.set('class', values.join(' ')) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/dataset.js: -------------------------------------------------------------------------------- 1 | 2 | var forOwn = require('lodash.forown') 3 | var escape = require('lodash.escape') 4 | 5 | // data.dataset 6 | 7 | module.exports = function datasetModule (vnode, attributes) { 8 | var dataset = vnode.data.dataset || {} 9 | 10 | forOwn(dataset, function (value, key) { 11 | attributes.set(`data-${key}`, escape(value)) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /modules/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | class: require('./class'), 4 | props: require('./props'), 5 | attributes: require('./attributes'), 6 | style: require('./style'), 7 | dataset: require('./dataset') 8 | } 9 | -------------------------------------------------------------------------------- /modules/props.js: -------------------------------------------------------------------------------- 1 | 2 | var forOwn = require('lodash.forown') 3 | var escape = require('lodash.escape') 4 | 5 | // https://developer.mozilla.org/en-US/docs/Web/API/element 6 | var omit = [ 7 | 'attributes', 8 | 'childElementCount', 9 | 'children', 10 | 'classList', 11 | 'clientHeight', 12 | 'clientLeft', 13 | 'clientTop', 14 | 'clientWidth', 15 | 'currentStyle', 16 | 'firstElementChild', 17 | 'innerHTML', 18 | 'lastElementChild', 19 | 'nextElementSibling', 20 | 'ongotpointercapture', 21 | 'onlostpointercapture', 22 | 'onwheel', 23 | 'outerHTML', 24 | 'previousElementSibling', 25 | 'runtimeStyle', 26 | 'scrollHeight', 27 | 'scrollLeft', 28 | 'scrollLeftMax', 29 | 'scrollTop', 30 | 'scrollTopMax', 31 | 'scrollWidth', 32 | 'tabStop', 33 | 'tagName' 34 | ] 35 | 36 | // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes 37 | var booleanAttributes = [ 38 | 'disabled', 39 | 'visible', 40 | 'checked', 41 | 'readonly', 42 | 'required', 43 | 'allowfullscreen', 44 | 'autofocus', 45 | 'autoplay', 46 | 'compact', 47 | 'controls', 48 | 'default', 49 | 'formnovalidate', 50 | 'hidden', 51 | 'ismap', 52 | 'itemscope', 53 | 'loop', 54 | 'multiple', 55 | 'muted', 56 | 'noresize', 57 | 'noshade', 58 | 'novalidate', 59 | 'nowrap', 60 | 'open', 61 | 'reversed', 62 | 'seamless', 63 | 'selected', 64 | 'sortable', 65 | 'truespeed', 66 | 'typemustmatch' 67 | ] 68 | 69 | // data.props 70 | 71 | module.exports = function propsModule (vnode, attributes) { 72 | var props = vnode.data.props || {} 73 | 74 | forOwn(props, function (value, key) { 75 | if (omit.indexOf(key) > -1) { 76 | return 77 | } 78 | if (key === 'htmlFor') { 79 | key = 'for' 80 | } 81 | if (key === 'className') { 82 | key = 'class' 83 | } 84 | 85 | var lkey = key.toLowerCase() 86 | if (~booleanAttributes.indexOf(lkey)) { 87 | if (value) { // set attr only when truthy 88 | attributes.set(lkey, lkey) 89 | } 90 | } else { 91 | attributes.set(lkey, escape(value)) 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /modules/style.js: -------------------------------------------------------------------------------- 1 | 2 | var assign = require('object-assign') 3 | var forOwn = require('lodash.forown') 4 | var escape = require('lodash.escape') 5 | var kebabCase = require('lodash.kebabcase') 6 | 7 | // data.style 8 | 9 | module.exports = function styleModule (vnode, attributes) { 10 | var values = [] 11 | var style = vnode.data.style || {} 12 | 13 | // merge in `delayed` properties 14 | if (style.delayed) { 15 | assign(style, style.delayed) 16 | } 17 | 18 | forOwn(style, function (value, key) { 19 | // omit hook objects 20 | if (typeof value === 'string' || typeof value === 'number') { 21 | var kebabKey = kebabCase(key) 22 | values.push((key.match(/^--.*/) ? '--' + kebabKey : kebabKey) + ': ' + escape(value)) 23 | } 24 | }) 25 | 26 | if (values.length) { 27 | attributes.set('style', values.join('; ')) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snabbdom-to-html", 3 | "version": "7.1.0", 4 | "description": "Render Snabbdom Vnodes to HTML strings", 5 | "main": "index.js", 6 | "typings": "./type-definitions/snabbdom-to-html.d.ts", 7 | "scripts": { 8 | "pretest": "standard", 9 | "test": "tape test/*.js", 10 | "disc": "browserify ./index.js --full-paths | discify --open" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/acstll/snabbdom-to-html.git" 15 | }, 16 | "keywords": [ 17 | "virtual", 18 | "dom", 19 | "html", 20 | "snabbdom" 21 | ], 22 | "author": "acstll", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/acstll/snabbdom-to-html/issues" 26 | }, 27 | "homepage": "https://github.com/acstll/snabbdom-to-html#readme", 28 | "dependencies": { 29 | "lodash.escape": "^4.0.1", 30 | "lodash.forown": "^4.4.0", 31 | "lodash.kebabcase": "^4.1.1", 32 | "lodash.remove": "^4.7.0", 33 | "lodash.uniq": "^4.5.0", 34 | "object-assign": "^4.1.0", 35 | "parse-sel": "^1.0.0" 36 | }, 37 | "devDependencies": { 38 | "browserify": "^17.0.0", 39 | "disc": "^1.3.2", 40 | "snabbdom": "^3.0.3", 41 | "standard": "^12.0.1", 42 | "tape": "^5.2.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var test = require('tape') 3 | var snabbdom = require('snabbdom') 4 | var toHTML = require('../') 5 | var init = require('../init') 6 | var modules = require('../modules') 7 | var h = snabbdom.h 8 | var thunk = snabbdom.thunk 9 | 10 | test('Main export', function (t) { 11 | t.equal(typeof toHTML, 'function', 'is function') 12 | t.equal(toHTML(h('i', { props: { title: 'Italics' } }, ':-)')), ':-)', 'and it works') 13 | 14 | t.end() 15 | }) 16 | 17 | test('No modules', function (t) { 18 | var vnode 19 | var renderToString = init([]) 20 | 21 | vnode = h('div') 22 | t.equal(renderToString(vnode), '
', 'no content') 23 | 24 | vnode = h('div', 'Hello World') 25 | t.equal(renderToString(vnode), '
Hello World
', 'tag and content') 26 | 27 | vnode = h('br') 28 | t.equal(renderToString(vnode), '
', 'void element') 29 | 30 | vnode = h('div', [ 31 | h('p', 'Paragraph 1'), 32 | h('p', [h('img')]), 33 | h('ul', [ 34 | h('li', '1'), 35 | h('li', '2'), 36 | h('li', '3') 37 | ]) 38 | ]) 39 | t.equal(renderToString(vnode), '

Paragraph 1

', 'nesting') 40 | 41 | vnode = h('div', [ 42 | undefined, 43 | null 44 | ]) 45 | t.equal(renderToString(vnode), '
', 'null / undefined') 46 | 47 | vnode = h('!', 'comment text') 48 | t.equal(renderToString(vnode), '', 'comment') 49 | 50 | t.end() 51 | }) 52 | 53 | test('Modules', function (t) { 54 | var vnode 55 | var html 56 | var renderToString = init([ 57 | modules.class, 58 | modules.props, 59 | modules.attributes, 60 | modules.style, 61 | modules.dataset 62 | ]) 63 | 64 | // style 65 | 66 | vnode = h('div', { 67 | style: { 68 | backgroundColor: 'cyan' 69 | } 70 | }) 71 | t.equal(renderToString(vnode), '
', 'style 1') 72 | 73 | vnode = h('div', { 74 | style: { 75 | color: 'red', 76 | fontSize: '2em', 77 | lineHeight: 1.3 78 | } 79 | }) 80 | t.equal(renderToString(vnode), '
', 'style 2') 81 | 82 | // `delayed` and hook properties 83 | 84 | vnode = h('div', { 85 | style: { 86 | fontSize: '100%', 87 | color: 'blue', 88 | delayed: { 89 | color: 'white' 90 | }, 91 | remove: { 92 | color: 'black' 93 | }, 94 | destroy: { 95 | color: 'cyan' 96 | } 97 | } 98 | }) 99 | t.equal(renderToString(vnode), '
', 'style 3') 100 | 101 | // props 102 | 103 | vnode = h('a', { 104 | props: { 105 | href: 'http://github.com', 106 | target: '_blank' 107 | } 108 | }, 'Github') 109 | t.equal(renderToString(vnode), 'Github', 'props 1') 110 | 111 | vnode = h('a#github', { 112 | props: { 113 | className: 'a b', 114 | href: 'http://github.com', 115 | target: '_blank' 116 | } 117 | }, 'Github') 118 | t.equal(renderToString(vnode), 'Github', 'props 2') 119 | 120 | vnode = h('a#github', { 121 | props: { 122 | id: 'overridden', 123 | href: 'http://github.com' 124 | } 125 | }, 'Github') 126 | t.equal(renderToString(vnode), 'Github', 'props 3, id override') 127 | 128 | vnode = h('a#github', { 129 | props: { 130 | innerHTML: 'Github', 131 | href: 'http://github.com', 132 | target: '_blank' 133 | } 134 | }, 'Github') 135 | t.equal(renderToString(vnode), 'Github', 'props 4, innerHTML') 136 | 137 | // attrs 138 | 139 | vnode = h('a', { 140 | attrs: { 141 | href: 'http://github.com', 142 | target: '_blank' 143 | } 144 | }, 'Github') 145 | t.equal(renderToString(vnode), 'Github', 'attrs 1') 146 | 147 | vnode = h('a#github', { 148 | attrs: { 149 | class: 'a b', 150 | href: 'http://github.com', 151 | target: '_blank' 152 | } 153 | }, 'Github') 154 | t.equal(renderToString(vnode), 'Github', 'attrs 2') 155 | 156 | vnode = h('img', { 157 | attrs: { 158 | src: 'https://test.com' 159 | }, 160 | class: { 161 | selected: 'true' 162 | } 163 | }) 164 | t.equal(renderToString(vnode), '', 'class truthy check') 165 | 166 | vnode = h('a#github', { 167 | attrs: { 168 | id: 'overridden', 169 | href: 'http://github.com' 170 | } 171 | }, 'Github') 172 | t.equal(renderToString(vnode), 'Github', 'attrs 3, id override') 173 | 174 | html = 175 | '' + 176 | 'Balls' + 177 | '' + 178 | '' + 179 | '' + 180 | '' 181 | vnode = h('svg', { 182 | attrs: { 183 | width: '92', 184 | height: '38', 185 | viewBox: '0 0 92 38', 186 | xmlns: 'http://www.w3.org/2000/svg', 187 | 'xmlns:xlink': 'http://www.w3.org/1999/xlink' 188 | } 189 | }, [ 190 | h('title', 'Balls'), 191 | h('g', { 192 | attrs: { 193 | fill: 'none', 194 | 'fill-rule': 'evenodd', 195 | stroke: '#979797', 196 | 'stroke-width': 3 197 | } 198 | }, [ 199 | h('circle', { props: { cx: '19', cy: '19', r: '17' } }), 200 | h('circle', { props: { cx: '73', cy: '19', r: '17' } }) 201 | ]) 202 | ]) 203 | t.equal(renderToString(vnode), html, 'svg') 204 | 205 | html = 'Hello world' 206 | vnode = h('svg', [ 207 | h('text', 'Hello world') 208 | ]) 209 | t.equal(renderToString(vnode), html, 'svg') 210 | 211 | html = '' 212 | vnode = h('svg', [ 213 | h('style', '.foo: { fill: red; }') 214 | ]) 215 | t.equal(renderToString(vnode), html, 'svg style') 216 | 217 | html = '' 218 | vnode = h('svg', [ 219 | h('script', 'var a = 42;') 220 | ]) 221 | t.equal(renderToString(vnode), html, 'svg script') 222 | 223 | vnode = h('label', { 224 | props: { 225 | htmlFor: 'beep' 226 | } 227 | }, [ 228 | 'Edge case ', 229 | h('input', { attrs: { type: 'text', value: 'Shit' } }) 230 | ]) 231 | t.equal(renderToString(vnode), '', 'htmlFor, nested tag and text together') 232 | 233 | // class 234 | 235 | vnode = h('p', { 236 | class: { 237 | yes: true, 238 | no: false 239 | } 240 | }, 'Text') 241 | t.equal(renderToString(vnode), '

Text

', 'class 1') 242 | 243 | vnode = h('p.yes.no', { 244 | class: { 245 | yes: true, 246 | no: false 247 | } 248 | }, 'Text') 249 | t.equal(renderToString(vnode), '

Text

', 'class 2') 250 | 251 | // classList behaviour 252 | vnode = h('p.yes.no', { 253 | class: { 254 | no: false, 255 | else: true 256 | } 257 | }, 'Text') 258 | t.equal(renderToString(vnode), '

Text

', 'class 3') 259 | 260 | vnode = h('p.yes.no', { 261 | attrs: { 262 | class: 'something else' 263 | } 264 | }, 'Text') 265 | t.equal(renderToString(vnode), '

Text

', 'class 4') 266 | 267 | // dataset 268 | 269 | vnode = h('div', { 270 | dataset: { 271 | foo: 'bar', 272 | answer: 42 273 | } 274 | }, '') 275 | t.equal(renderToString(vnode), '
', 'dataset 1') 276 | 277 | // altogether "randomly" 278 | 279 | vnode = h('h1#happy.regular', { props: { title: 'Cheers' } }, 'Happy Birthday') 280 | t.equal(renderToString(vnode), '

Happy Birthday

', 'altogether') 281 | 282 | t.end() 283 | }) 284 | 285 | test('Protect against `data` being undefined', function (t) { 286 | var vnode = h('div') 287 | vnode.data = undefined 288 | 289 | t.doesNotThrow(function () { 290 | return toHTML(vnode) 291 | }) 292 | 293 | t.end() 294 | }) 295 | 296 | test('Support thunks', function (t) { 297 | var vnode = thunk('span', numberInSpan, [22]) 298 | 299 | function numberInSpan (n) { 300 | return h('span', 'Number is ' + n) 301 | } 302 | 303 | t.equal(toHTML(vnode), 'Number is 22') 304 | 305 | t.end() 306 | }) 307 | 308 | test('Custom CSS properties', function (t) { 309 | var vnode = h('div', { style: { '--customColor': '#000' } }) 310 | 311 | t.equal(toHTML(vnode), '
') 312 | 313 | t.end() 314 | }) 315 | 316 | test('Escape HTML in text', function (t) { 317 | var vnode = h('div', ['

']) 318 | 319 | t.equal(toHTML(vnode), '
<p></p>
') 320 | 321 | vnode = h('div', '

') 322 | 323 | t.equal(toHTML(vnode), '
<p></p>
') 324 | 325 | t.end() 326 | }) 327 | 328 | test('Empty string in children', function (t) { 329 | var vnode = h('span', ['']) 330 | var htmlString = '' 331 | 332 | t.equal(toHTML(vnode), htmlString) 333 | 334 | vnode = h('span', []) 335 | 336 | t.equal(toHTML(vnode), htmlString) 337 | 338 | vnode = h('span', '') 339 | 340 | t.equal(toHTML(vnode), htmlString) 341 | 342 | t.end() 343 | }) 344 | 345 | test('Boolean attributes', function (t) { 346 | // truthy case 347 | var vnode = h('input', { props: { type: 'checkbox', checked: true } }) 348 | var htmlString = '' 349 | 350 | t.equal(toHTML(vnode), htmlString) 351 | 352 | // falsy case 353 | vnode = h('input', { props: { type: 'checkbox', checked: false } }) 354 | htmlString = '' 355 | 356 | t.equal(toHTML(vnode), htmlString) 357 | 358 | t.end() 359 | }) 360 | 361 | test('svg container elements', function (t) { 362 | var vnode = h('svg', 363 | { attrs: { xmlns: 'http://www.w3.org/2000/svg' } }, 364 | [h('defs', [h('clipPath', [h('rect', { attrs: { x: 0, y: 1, width: 2, height: 3 } })])])] 365 | ) 366 | var htmlString = '' 367 | 368 | t.equal(toHTML(vnode), htmlString) 369 | 370 | vnode = h('svg', [ 371 | h('linearGradient', [ 372 | h('stop') 373 | ]) 374 | ]) 375 | htmlString = '' 376 | t.equal(toHTML(vnode), htmlString) 377 | 378 | t.end() 379 | }) 380 | -------------------------------------------------------------------------------- /type-definitions/snabbdom-to-html.d.ts: -------------------------------------------------------------------------------- 1 | declare module "snabbdom-to-html-common" { 2 | import {VNode} from "snabbdom/vnode" 3 | 4 | interface Module { 5 | (vnode: VNode, attributes: Map): void; 6 | } 7 | 8 | interface ModuleIndex { 9 | class: Module; 10 | props: Module; 11 | attributes: Module; 12 | style: Module; 13 | } 14 | 15 | export { 16 | VNode, 17 | ModuleIndex, 18 | Module 19 | } 20 | } 21 | 22 | declare module "snabbdom-to-html" { 23 | import {VNode} from "snabbdom-to-html-common"; 24 | function toHTML(vnode: VNode): string; 25 | export = toHTML 26 | } 27 | 28 | declare module "snabbdom-to-html/init" { 29 | import {VNode, Module, ModuleIndex} from "snabbdom-to-html-common"; 30 | function init (modules: Module[]): (vnode: VNode) => string; 31 | export = init 32 | } 33 | 34 | 35 | declare module "snabbdom-to-html/modules" { 36 | import {ModuleIndex} from "snabbdom-to-html-common"; 37 | let modules: ModuleIndex; 38 | export = modules 39 | } 40 | 41 | declare module "snabbdom-to-html/modules/index" { 42 | import {ModuleIndex} from "snabbdom-to-html-common"; 43 | let modules: ModuleIndex; 44 | export = modules 45 | } 46 | 47 | declare module "snabbdom-to-html/modules/attributes" { 48 | import {Module} from "snabbdom-to-html-common"; 49 | let attrModule: Module; 50 | export = attrModule; 51 | } 52 | 53 | declare module "snabbdom-to-html/modules/class" { 54 | import {Module} from "snabbdom-to-html-common"; 55 | let classModule: Module; 56 | export = classModule; 57 | } 58 | 59 | declare module "snabbdom-to-html/modules/props" { 60 | import {Module} from "snabbdom-to-html-common"; 61 | let propsModule: Module; 62 | export = propsModule; 63 | } 64 | 65 | declare module "snabbdom-to-html/modules/style" { 66 | import {Module} from "snabbdom-to-html-common"; 67 | let styleModule: Module; 68 | export = styleModule; 69 | } 70 | --------------------------------------------------------------------------------