├── .npmignore ├── .gitignore ├── react.js ├── Makefile ├── package.json ├── Readme.md ├── History.md ├── make.js ├── index.js ├── elements.json └── test ├── browser.js └── sun.js /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /react.js: -------------------------------------------------------------------------------- 1 | console.log('Sun\'s react bindings are not implemented yet. Accepting PRs :-D') 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: browser 3 | @./node_modules/.bin/mocha \ 4 | --reporter spec \ 5 | test/sun.js 6 | 7 | browser: 8 | @./node_modules/.bin/devtool node_modules/mocha/bin/_mocha -qc -- -b test/browser.js 9 | 10 | .PHONY: test 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sun", 3 | "version": "1.1.4", 4 | "description": "Tiny little VDOM node helper for Preact", 5 | "keywords": [ 6 | "virtual", 7 | "dom", 8 | "preact", 9 | "hyperscript" 10 | ], 11 | "author": "Matthew Mueller ", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/MatthewMueller/sun.git" 15 | }, 16 | "dependencies": { 17 | "flatten": "1.0.2", 18 | "object-assign": "4.1.0", 19 | "sliced": "1.0.1" 20 | }, 21 | "devDependencies": { 22 | "babel-eslint": "6.1.2", 23 | "devtool": "2.2.0", 24 | "html-element-attributes": "1.0.0", 25 | "html-tag-names": "1.0.0", 26 | "lodash.flatten": "4.4.0", 27 | "lodash.uniq": "4.5.0", 28 | "mocha": "3.0.2", 29 | "object-values": "1.0.0", 30 | "preact-render-to-string": "3.1.1", 31 | "snazzy": "5.0.0" 32 | }, 33 | "peerDependencies": { 34 | "preact": "*" 35 | }, 36 | "main": "index", 37 | "scripts": { 38 | "lint": "snazzy", 39 | "pretest": "npm run lint", 40 | "test": "make test" 41 | }, 42 | "standard": { 43 | "parser": "babel-eslint", 44 | "globals": [ 45 | "describe", 46 | "it", 47 | "before", 48 | "beforeEach", 49 | "after", 50 | "afterEach" 51 | ] 52 | } 53 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # sun 3 | 4 | Simple little virtual DOM node builder for Preact. 5 | 6 | ## Example 7 | 8 | ```js 9 | let render = require('preact-render-to-string') 10 | let { div, span, strong } = require('sun') 11 | 12 | const App = ({ name }) => ( 13 | div.class('App')( 14 | span('hello'), 15 | strong(name) 16 | ) 17 | ) 18 | 19 | render(App({ name: 'matt' })) 20 | //
hellomatt
21 | ``` 22 | 23 | ## Features 24 | 25 | - Functions for all valid HTML elements 26 | - Functions for all valid HTML attributes on each element 27 | - Supports custom attributes (e.g. `span({ custom: 'attribute' })('hi there!')`) 28 | - Proudly built for [Preact](https://github.com/developit/preact) 29 | 30 | ## Installation 31 | 32 | ```bash 33 | npm install sun 34 | ``` 35 | 36 | ## High-Order Components 37 | 38 | High-Order Components are a powerful technique for modifying 39 | children on the fly. Here's how you can do it with sun. 40 | 41 | ```js 42 | let render = require('preact-render-to-string') 43 | let { component } = require('sun') 44 | 45 | let styling = component(function ({ class: cls, children }) { 46 | assert.equal(cls, 'whatever') 47 | return children[0] 48 | }) 49 | 50 | let app = styling.class('whatever')( 51 | div.class('wahtever')( 52 | strong('hi') 53 | ) 54 | ) 55 | 56 | assert.equal(render(app), '
hi
') 57 | ``` 58 | 59 | ## Where you can Help 60 | 61 | - Performance tuning 62 | - React support 63 | 64 | ## Test 65 | 66 | ```bash 67 | npm install 68 | make test 69 | ``` 70 | 71 | ## License 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.1.4 / 2016-10-23 3 | ================== 4 | 5 | * support passing an array of classes in 6 | 7 | 1.1.3 / 2016-10-19 8 | ================== 9 | 10 | * fix for undefined, falsey, null and empty string children 11 | 12 | 1.1.2 / 2016-10-19 13 | ================== 14 | 15 | * update to support the key attribute 16 | 17 | 1.1.1 / 2016-10-18 18 | ================== 19 | 20 | * greatly compress the html tags and attributes. build them on the client 21 | 22 | 1.1.0 / 2016-10-10 23 | ================== 24 | 25 | * now passes through children on the client, but still renders on the server. also added onMount and onUnmount attributes 26 | 27 | 1.0.9 / 2016-09-26 28 | ================== 29 | 30 | * remove preact as a dep 31 | 32 | 1.0.8 / 2016-09-26 33 | ================== 34 | 35 | * make preact a peer dependency 36 | * add browser tests 37 | * add a few more tests 38 | 39 | 1.0.7 / 2016-09-21 40 | ================== 41 | 42 | * bump dependencies 43 | 44 | 1.0.6 / 2016-09-01 45 | ================== 46 | 47 | * support passing functions through 48 | 49 | 1.0.5 / 2016-08-30 50 | ================== 51 | 52 | * support an list of values 53 | 54 | 1.0.4 / 2016-08-28 55 | ================== 56 | 57 | * flatten arrays of arrays 58 | 59 | 1.0.3 / 2016-08-25 60 | ================== 61 | 62 | * support high-order components 63 | 64 | 1.0.2 / 2016-08-24 65 | ================== 66 | 67 | * support the same events as react 68 | 69 | 1.0.1 / 2016-08-05 70 | ================== 71 | 72 | * fix for empty elements, 1 child and objects with a toString() fn 73 | * fix parent 74 | * no react support atm 75 | 76 | 1.0.0 / 2016-08-04 77 | ================== 78 | 79 | * Initial Release 80 | -------------------------------------------------------------------------------- /make.js: -------------------------------------------------------------------------------- 1 | const Attributes = require('html-element-attributes') 2 | const Tags = require('html-tag-names') 3 | const flatten = require('lodash.flatten') 4 | const values = require('object-values') 5 | const uniq = require('lodash.uniq') 6 | const fs = require('fs') 7 | 8 | const Events = [ 9 | 'key', 10 | 'onMount', 'onUnmount', 11 | 'onCopy', 'onCut', 'onPaste', 12 | 'onCompositionEnd', 'onCompositionStart', 'onCompositionUpdate', 13 | 'onKeyDown', 'onKeyPress', 'onKeyUp', 14 | 'onFocus', 'onBlur', 15 | 'onChange', 'onInput', 'onSubmit', 16 | 'onClick', 'onContextMenu', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 17 | 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 18 | 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 19 | 'onSelect', 20 | 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 21 | 'onScroll', 22 | 'onWheel', 23 | 'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 24 | 'onEnded', 'onError', 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 25 | 'onPlaying', 'onProgress', 'onRateChange', 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 26 | 'onTimeUpdate', 'onVolumeChange', 'onWaiting', 27 | 'onLoad', 28 | 'onAnimationStart', 'onAnimationEnd', 'onAnimationIteration', 29 | 'onTransitionEnd' 30 | ] 31 | let all = Attributes['*'].concat(Events) 32 | // console.log(Attributes.meta) 33 | delete Attributes['*'] 34 | 35 | let attributes = uniq(flatten(values(Attributes))) 36 | // console.log(attributes.indexOf('content')) 37 | let tags = [].concat(Tags) 38 | 39 | let map = tags.map(function (tag) { 40 | let attrs = Attributes[tag] 41 | if (!attrs) return 0 42 | return attrs.map(function (attr) { 43 | let i = attributes.indexOf(attr) 44 | if (!~i) throw new Error('attr not found: ' + attr) 45 | return i 46 | }) 47 | }) 48 | 49 | // console.log(tags.indexOf('meta')) 50 | // console.log(map[84].map(i => attributes[i])) 51 | // console.log(map) 52 | fs.writeFileSync(__dirname + '/elements.json', JSON.stringify([ tags, map, all, attributes])) 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | const assign = require('object-assign') 6 | const flatten = require('flatten') 7 | const sliced = require('sliced') 8 | const slice = require('sliced') 9 | const { h } = require('preact') 10 | 11 | /** 12 | * Decifer the elements 13 | */ 14 | 15 | const [ 16 | tags, 17 | index, 18 | all, 19 | attributes 20 | ] = require('./elements.json') 21 | 22 | const Attributes = index.reduce((attrs, keys, i) => { 23 | const tag = tags[i] 24 | if (keys) attrs[tag] = all.concat(keys.map(k => attributes[k])) 25 | else attrs[tag] = all 26 | return attrs 27 | }, {}) 28 | 29 | /** 30 | * isBrowser 31 | */ 32 | 33 | const isBrowser = typeof window !== 'undefined' 34 | 35 | /** 36 | * Utils 37 | */ 38 | 39 | const isObject = v => Object.prototype.toString.call(v) === '[object Object]' 40 | const isClass = (v) => /class(name)?/i.test(v) 41 | const has = (o, v) => o.hasOwnProperty(v) 42 | const isArray = v => Array.isArray(v) 43 | const truthy = (v) => !!v 44 | 45 | /** 46 | * Create functions from all the tags 47 | */ 48 | 49 | tags.forEach(name => { exports[name] = Component(name) }) 50 | 51 | /** 52 | * Create a custom component 53 | */ 54 | 55 | exports.component = Component 56 | 57 | /** 58 | * Override HTML 59 | */ 60 | 61 | const html = exports.html 62 | exports.html = function (mixed) { 63 | if (!isBrowser) { 64 | return html.apply(html, arguments) 65 | } else if (mixed.nodeName || arguments.length > 1) { 66 | let nodes = flatten(slice(arguments)) 67 | let body = nodes.filter(function (node) { return node.nodeName === 'body' })[0] 68 | if (!body || !body.children) return html.apply(html, arguments) 69 | else return body.children[0] 70 | } else { 71 | return html.apply(html, arguments) 72 | } 73 | } 74 | 75 | /** 76 | * Create a component 77 | */ 78 | 79 | function Component (name) { 80 | let attributes = [].concat(Attributes[name] || all) 81 | 82 | function Tag () { 83 | let attrs = {} 84 | 85 | function tag (mixed) { 86 | if (!arguments.length || (!mixed && mixed !== 0)) { 87 | return h(name, attrs) 88 | } else if (mixed.nodeName || arguments.length > 1) { 89 | return h(name, attrs, flatten(slice(arguments))) 90 | } else if (isArray(mixed)) { 91 | return h(name, attrs, flatten(mixed)) 92 | } else if (!isObject(mixed)) { 93 | return h(name, attrs, mixed) 94 | } else if (has(mixed, 'toString')) { 95 | return h(name, attrs, String(mixed)) 96 | } 97 | 98 | // attributes 99 | attrs = assign(attrs, mixed) 100 | return tag 101 | } 102 | 103 | // attach instance functions 104 | attributes.forEach(attr => { tag[attr] = IAttr(tag, attrs, attr) }) 105 | tag.toJSON = () => h(name, attrs) 106 | 107 | // create an instance of the tag 108 | return tag.apply(null, arguments) 109 | } 110 | 111 | // attach static functions 112 | attributes.forEach(attr => { Tag[attr] = Attr(Tag, attr) }) 113 | Tag.toJSON = () => h(name) 114 | 115 | return Tag 116 | } 117 | 118 | /** 119 | * Attribute builder function for all tags 120 | */ 121 | 122 | function Attr (fn, attr) { 123 | return function (value) { 124 | let attrs = {} 125 | attrs[attr] = isClass(attr) 126 | ? flatten(sliced(arguments)).filter(truthy).join(' ') 127 | : value 128 | return fn(attrs) 129 | } 130 | } 131 | 132 | /** 133 | * Attribute builder for all tag instances 134 | */ 135 | 136 | function IAttr (fn, attrs, attr) { 137 | return function (value) { 138 | attrs[attr] = isClass(attr) 139 | ? flatten(sliced(arguments)).filter(truthy).join(' ') 140 | : value 141 | return fn 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /elements.json: -------------------------------------------------------------------------------- 1 | [["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","bgsound","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","content","data","datalist","dd","del","details","dfn","dialog","dir","div","dl","dt","element","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","image","img","input","ins","isindex","kbd","keygen","label","legend","li","link","listing","main","map","mark","marquee","math","menu","menuitem","meta","meter","multicol","nav","nextid","nobr","noembed","noframes","noscript","object","ol","optgroup","option","output","p","param","picture","plaintext","pre","progress","q","rb","rbc","rp","rt","rtc","ruby","s","samp","script","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","svg","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp"],[[52,53,54,55,56,20,57,58,59,60,22,49],0,0,0,[1,23,67,68,69,8,70,20,71,72,13],[23,53,54,55,56,61,57,58,60,22,49],0,0,[130,131,82,132,133,134,135,47],0,[55,22],[111,112,46],0,0,0,0,0,[108],[98,99,3,100,101,102],[110],[24,27,28,29,30,31,32,33,62,20,49,51],[8,13],[1],0,0,0,[1,4,5,97,12,13],[1,4,5,97,12,13],0,0,[51],0,0,[108,109],[145],0,[145],[113],[1],[113],0,0,0,[8,47,49,13],[27,28,20],0,0,[111,112,46],0,[14,15,16,17,18,19,20,21,22],[74,75,76,77,20,121,79,47],[64,65],[1],[1],[1],[1],[1],[1],[124],0,0,[1,96,46,13],[126,127],0,[1,73,74,8,75,76,77,20,78,79,47,80,13],0,[1,23,81,82,8,70,35,75,20,83,47,84,50,72,13],[14,1,23,17,24,25,26,27,28,29,30,31,32,33,8,34,35,36,37,38,39,40,41,20,42,43,44,45,46,47,48,49,50,51,13],[108,109],[125],0,[24,128,27,28,129,20],[63,28],[1],[49,51],[52,82,55,56,103,58,59,83,22,49],0,0,[20],0,0,0,[113,114,49],[25,137,27,143,114,144,49],[52,117,118,20,119],[140,141,37,39,142,51],0,0,0,0,0,0,0,[1,67,81,85,69,86,87,88,28,8,70,20,89,49,90,50,72,13],[113,115,116,49],[27,114],[27,114,120,51],[63,28,20],[1],[20,49,51,123],0,0,[13],[37,51],[108],0,0,0,0,0,0,0,0,[104,52,82,105,106,107,47,49],0,[17,24,27,28,41,20,45,46],0,0,[103,83,47,84,49],0,0,0,0,[103,107,122,49],0,0,0,0,[1,3,81,91,92,93,94,95,13],[1,4,5,12],[0,1,2,3,4,5,6,7,8,9,10,11,12,13],0,[17,24,64,26,27,28,34,38,40,20,43,44,45,65,66],[1,4,5,12],[0,1,2,3,4,5,6,7,8,9,10,11,12,13],[1,4,5,12],[109],0,[1,3,4,5,12],[137,138,114,47,139],0,0,[113,49],0,[130,131,82,8,132,133,134,136,135,47,13],0,0],["accesskey","class","contenteditable","contextmenu","dir","draggable","dropzone","hidden","id","itemid","itemprop","itemref","itemscope","itemtype","lang","spellcheck","style","tabindex","title","translate","key","onMount","onUnmount","onCopy","onCut","onPaste","onCompositionEnd","onCompositionStart","onCompositionUpdate","onKeyDown","onKeyPress","onKeyUp","onFocus","onBlur","onChange","onInput","onSubmit","onClick","onContextMenu","onDoubleClick","onDrag","onDragEnd","onDragEnter","onDragExit","onDragLeave","onDragOver","onDragStart","onDrop","onMouseDown","onMouseEnter","onMouseLeave","onMouseMove","onMouseOut","onMouseOver","onMouseUp","onSelect","onTouchCancel","onTouchEnd","onTouchMove","onTouchStart","onScroll","onWheel","onAbort","onCanPlay","onCanPlayThrough","onDurationChange","onEmptied","onEncrypted","onEnded","onError","onLoadedData","onLoadedMetadata","onLoadStart","onPause","onPlay","onPlaying","onProgress","onRateChange","onSeeked","onSeeking","onStalled","onSuspend","onTimeUpdate","onVolumeChange","onWaiting","onLoad","onAnimationStart","onAnimationEnd","onAnimationIteration","onTransitionEnd"],["abbr","align","axis","bgcolor","char","charoff","colspan","headers","height","nowrap","rowspan","scope","valign","width","accept","accept-charset","action","autocomplete","enctype","method","name","novalidate","target","alt","autofocus","checked","dirname","disabled","form","formaction","formenctype","formmethod","formnovalidate","formtarget","inputmode","ismap","list","max","maxlength","min","minlength","multiple","pattern","placeholder","readonly","required","size","src","step","type","usemap","value","charset","coords","download","href","hreflang","ping","rel","rev","shape","nohref","menu","for","cols","rows","wrap","archive","code","codebase","hspace","object","vspace","allowfullscreen","frameborder","longdesc","marginheight","marginwidth","sandbox","scrolling","srcdoc","border","crossorigin","sizes","srcset","classid","codetype","data","declare","standby","typemustmatch","cellpadding","cellspacing","frame","rules","summary","noshade","span","alink","background","link","text","vlink","media","async","defer","language","nonce","cite","datetime","clear","color","face","compact","label","reversed","start","content","http-equiv","scheme","selected","noresize","scoped","valuetype","profile","prompt","manifest","version","challenge","keytype","autoplay","controls","loop","mediagroup","muted","preload","poster","default","kind","srclang","high","low","optimum","icon","radiogroup","open"]] 2 | -------------------------------------------------------------------------------- /test/browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | let { div, span, strong, component, html, head, body, meta, link, title } = require('..') 6 | let { h, render } = require('preact') 7 | let assert = require('assert') 8 | 9 | describe('sun', function () { 10 | it('should work with basic text', function () { 11 | let d = div('hi') 12 | assert.equal(r(d), '
hi
') 13 | }) 14 | 15 | it('should work with an object of attributes', function () { 16 | let d = div({ fruit: 'apple' })('hi') 17 | assert.equal(r(d), '
hi
') 18 | }) 19 | 20 | it('should work with children', function () { 21 | let d = div({ fruit: 'orange' })([ 22 | span('hi'), 23 | div({ world: true })('world') 24 | ]) 25 | 26 | assert.equal(r(d), '
hi
world
') 27 | }) 28 | 29 | it('should work with undefined', () => { 30 | let d = div(undefined) 31 | assert.equal(r(d), '
') 32 | }) 33 | 34 | it('should work with false', () => { 35 | let d = div(false) 36 | assert.equal(r(d), '
') 37 | }) 38 | 39 | it('should work with 0', () => { 40 | let d = div(0) 41 | assert.equal(r(d), '
0
') 42 | }) 43 | 44 | it('should work with null', () => { 45 | let d = div(null) 46 | assert.equal(r(d), '
') 47 | }) 48 | 49 | it('should work with empty strings', () => { 50 | let d = div('') 51 | assert.equal(r(d), '
') 52 | }) 53 | 54 | it('should support 1 sun child', function () { 55 | let d = div(span('hi')) 56 | assert.equal(r(d), '
hi
') 57 | }) 58 | 59 | it('should stringify objects with toString()', function () { 60 | let d = div(span({ toString: () => 'hi' })) 61 | assert.equal(r(d), '
hi
') 62 | }) 63 | 64 | it('should handle custom attributes with booleans', function () { 65 | let d = div({ custom: true })('hi') 66 | assert.equal(r(d), '
hi
') 67 | }) 68 | 69 | it('should handle empty tags with attributes', function () { 70 | let d = div({ custom: true })() 71 | assert.equal(r(d), '
') 72 | }) 73 | 74 | it('should handle empty tags', function () { 75 | assert.equal(r(div()), '
') 76 | }) 77 | 78 | it('should handle classes with text', function () { 79 | let d = div.class('orange').id('hi')({ fruit: 'orange' })('hi') 80 | assert.equal(r(d), '
hi
') 81 | }) 82 | 83 | it('should handle classes with children', function () { 84 | let d = div.class('orange').id('hi')({ fruit: 'orange' })([ 85 | span('hi'), 86 | div({ world: true })('world') 87 | ]) 88 | assert.equal(r(d), '
hi
world
') 89 | }) 90 | 91 | it("should work with what's on the readme", function () { 92 | const App = ({ name }) => ( 93 | div.class('App')( 94 | span('hello'), 95 | strong('matt') 96 | ) 97 | ) 98 | assert.equal(r(App({ name: 'matt' })), '
hellomatt
') 99 | }) 100 | 101 | it('should have the events', function () { 102 | let a = function (a) {} 103 | let b = function (b) {} 104 | let vnode = div.onClick(a).onMouseDown(b)() 105 | assert.equal(vnode.attributes.onClick.toString(), a.toString()) 106 | assert.equal(vnode.attributes.onMouseDown.toString(), b.toString()) 107 | }) 108 | 109 | it('should support high order component functions', function () { 110 | let styling = component(function ({ class: cls, children }) { 111 | assert.equal(cls, 'whatever') 112 | return children[0] 113 | }) 114 | 115 | let s = styling.class('whatever')( 116 | div.class('wahtever')( 117 | strong('hi') 118 | ) 119 | ) 120 | 121 | assert.equal(r(s), '
hi
') 122 | }) 123 | 124 | it('should support arrays of arrays', function () { 125 | let todos = [ { title: 'a' }, { title: 'b' } ] 126 | let d = div({ fruit: 'orange' })( 127 | span('hi'), 128 | todos.map(todo => strong(todo.title)) 129 | ) 130 | assert.equal(r(d), '
hiab
') 131 | }) 132 | 133 | it('should support a list of classes', function () { 134 | let d = div.class(true && 'a', false && 'b', 'c' || 'd')() 135 | assert.equal(r(d), '
') 136 | }) 137 | 138 | it('should support passing an array of classes', () => { 139 | let d = div.class(['a', false], 'c')() 140 | assert.equal(r(d), '
') 141 | }) 142 | 143 | it('should support passing functions in', function () { 144 | let fn = function () {} 145 | let d = div.onClick(fn)() 146 | assert.equal(d.attributes.onClick, fn) 147 | assert.equal(typeof d.attributes.onClick, 'function') 148 | }) 149 | 150 | it('should work a vnode child', () => { 151 | let d = div(h('h2', { class: 'blue' }, ['hi there!'])) 152 | assert.equal(r(d), '

hi there!

') 153 | }) 154 | 155 | it('should work vnode children', () => { 156 | let d = div([ 157 | h('h2', { class: 'blue' }, [ 158 | h('strong', {}, [ 159 | 'hi there!' 160 | ]) 161 | ]) 162 | ]) 163 | assert.equal(r(d), '

hi there!

') 164 | }) 165 | 166 | it('should ignore html, head & body tags', () => { 167 | let d = html( 168 | head( 169 | title('hello world!'), 170 | meta({ name: 'description' }).content('some description')(), 171 | link.href('index.css').rel('stylesheet')() 172 | ), 173 | body( 174 | div('hello world!') 175 | ) 176 | ) 177 | 178 | assert.equal(r(d), '
hello world!
') 179 | }) 180 | 181 | it('should support mounts', () => { 182 | assert.equal(typeof div.onMount, 'function') 183 | assert.equal(typeof div.onUnmount, 'function') 184 | }) 185 | 186 | it('should support keys', () => { 187 | assert.equal(typeof div.key, 'function') 188 | }) 189 | }) 190 | 191 | function r (v) { 192 | document.body.innerHTML = '' 193 | render(v, document.body) 194 | return document.body.innerHTML 195 | } 196 | -------------------------------------------------------------------------------- /test/sun.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | let { div, span, strong, component, html, head, title, meta, link, body } = require('..') 6 | let render = require('preact-render-to-string') 7 | let assert = require('assert') 8 | let { h } = require('preact') 9 | 10 | describe('sun', function () { 11 | it('should work with basic text', function () { 12 | let d = div('hi') 13 | assert.equal(render(d), '
hi
') 14 | }) 15 | 16 | it('should work with an object of attributes', function () { 17 | let d = div({ fruit: 'apple' })('hi') 18 | assert.equal(render(d), '
hi
') 19 | }) 20 | 21 | it('should work with children', function () { 22 | let d = div({ fruit: 'orange' })([ 23 | span('hi'), 24 | div({ world: true })('world') 25 | ]) 26 | 27 | assert.equal(render(d), '
hi
world
') 28 | }) 29 | 30 | it('should support 1 sun child', function () { 31 | let d = div(span('hi')) 32 | assert.equal(render(d), '
hi
') 33 | }) 34 | 35 | it('should stringify objects with toString()', function () { 36 | let d = div(span({ toString: () => 'hi' })) 37 | assert.equal(render(d), '
hi
') 38 | }) 39 | 40 | it('should handle custom attributes with booleans', function () { 41 | let d = div({ custom: true })('hi') 42 | assert.equal(render(d), '
hi
') 43 | }) 44 | 45 | it('should handle empty tags with attributes', function () { 46 | let d = div({ custom: true })() 47 | assert.equal(render(d), '
') 48 | }) 49 | 50 | it('should work with undefined', () => { 51 | let d = div(undefined) 52 | assert.equal(render(d), '
') 53 | }) 54 | 55 | it('should work with false', () => { 56 | let d = div(false) 57 | assert.equal(render(d), '
') 58 | }) 59 | 60 | it('should work with 0', () => { 61 | let d = div(0) 62 | assert.equal(render(d), '
0
') 63 | }) 64 | 65 | it('should work with null', () => { 66 | let d = div(null) 67 | assert.equal(render(d), '
') 68 | }) 69 | 70 | it('should work with empty strings', () => { 71 | let d = div('') 72 | assert.equal(render(d), '
') 73 | }) 74 | 75 | it('should handle empty tags', function () { 76 | assert.equal(render(div()), '
') 77 | }) 78 | 79 | it('should handle classes with text', function () { 80 | let d = div.class('orange').id('hi')({ fruit: 'orange' })('hi') 81 | assert.equal(render(d), '
hi
') 82 | }) 83 | 84 | it('should handle classes with children', function () { 85 | let d = div.class('orange').id('hi')({ fruit: 'orange' })([ 86 | span('hi'), 87 | div({ world: true })('world') 88 | ]) 89 | assert.equal(render(d), '
hi
world
') 90 | }) 91 | 92 | it("should work with what's on the readme", function () { 93 | const App = ({ name }) => ( 94 | div.class('App')( 95 | span('hello'), 96 | strong('matt') 97 | ) 98 | ) 99 | assert.equal(render(App({ name: 'matt' })), '
hellomatt
') 100 | }) 101 | 102 | it('should have the events', function () { 103 | let a = function (a) {} 104 | let b = function (b) {} 105 | let vnode = div.onClick(a).onMouseDown(b)() 106 | assert.equal(vnode.attributes.onClick.toString(), a.toString()) 107 | assert.equal(vnode.attributes.onMouseDown.toString(), b.toString()) 108 | }) 109 | 110 | it('should support high order component functions', function () { 111 | let styling = component(function ({ class: cls, children }) { 112 | assert.equal(cls, 'whatever') 113 | return children[0] 114 | }) 115 | 116 | let s = styling.class('whatever')( 117 | div.class('wahtever')( 118 | strong('hi') 119 | ) 120 | ) 121 | 122 | assert.equal(render(s), '
hi
') 123 | }) 124 | 125 | it('should support arrays of arrays', function () { 126 | let todos = [ { title: 'a' }, { title: 'b' } ] 127 | let d = div({ fruit: 'orange' })( 128 | span('hi'), 129 | todos.map(todo => strong(todo.title)) 130 | ) 131 | assert.equal(render(d), '
hiab
') 132 | }) 133 | 134 | it('should support a list of classes', function () { 135 | let d = div.class(true && 'a', false && 'b', 'c' || 'd')() 136 | assert.equal(render(d), '
') 137 | }) 138 | 139 | it('should support passing an array of classes', () => { 140 | let d = div.class(['a', false], 'c')() 141 | assert.equal(render(d), '
') 142 | }) 143 | 144 | it('should support passing functions in', function () { 145 | let fn = function () {} 146 | let d = div.onClick(fn)() 147 | assert.equal(d.attributes.onClick, fn) 148 | assert.equal(typeof d.attributes.onClick, 'function') 149 | }) 150 | 151 | it('should work a vnode child', () => { 152 | let d = div(h('h2', { class: 'blue' }, ['hi there!'])) 153 | assert.equal(render(d), '

hi there!

') 154 | }) 155 | 156 | it('should work vnode children', () => { 157 | let d = div([ 158 | h('h2', { class: 'blue' }, ['2']), 159 | h('h3', { class: 'blue' }, ['3']) 160 | ]) 161 | 162 | assert.equal(render(d), '

2

3

') 163 | }) 164 | 165 | it('should ignore html, head & body tags', () => { 166 | let d = html( 167 | head( 168 | title('hello world!'), 169 | meta({ name: 'description' }).content('some description')(), 170 | link.href('index.css').rel('stylesheet')() 171 | ), 172 | body( 173 | div('hello world!') 174 | ) 175 | ) 176 | 177 | assert.equal(render(d), 'hello world!
hello world!
') 178 | }) 179 | 180 | it('should support mounts', () => { 181 | assert.equal(typeof div.onMount, 'function') 182 | assert.equal(typeof div.onUnmount, 'function') 183 | }) 184 | 185 | it('should support keys', () => { 186 | assert.equal(typeof div.key, 'function') 187 | }) 188 | }) 189 | --------------------------------------------------------------------------------