├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── license.md ├── package.json ├── readme.md ├── src └── index.js └── tests └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dustinspecker/esnext", 3 | plugins: [ 4 | "no-use-extend-native" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | lib/ 3 | node_modules/ 4 | .nyc_output 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '5' 5 | - '4' 6 | notifications: 7 | email: false 8 | before_script: 9 | - npm prune 10 | script: 11 | - npm run test 12 | after_success: 13 | - 'curl -Lo travis_after_all.py https://git.io/travis_after_all' 14 | - python travis_after_all.py 15 | - 'export $(cat .to_export_back) &> /dev/null' 16 | - npm run-script coveralls 17 | - npm run semantic-release 18 | branches: 19 | except: 20 | - "/^v\\d+\\.\\d+\\.\\d+$/" 21 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dustin Specker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dscript", 3 | "version": "0.0.0-semantic-release", 4 | "description": "Framework agnostic hyperscript", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "compile": "babel src --out-dir lib", 8 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 9 | "lint": "eslint ./ --ignore-pattern node_modules/ --ignore-pattern lib", 10 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 11 | "test": "npm run lint && npm run compile && nyc ava" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/dustinspecker/dscript.git" 16 | }, 17 | "keywords": [ 18 | "dscript", 19 | "hyperscript", 20 | "virtual", 21 | "deku", 22 | "react" 23 | ], 24 | "author": { 25 | "name": "Dustin Specker", 26 | "email": "DustinSpecker@DustinSpecker.com", 27 | "url": "https://github.com/dustinspecker" 28 | }, 29 | "license": "MIT", 30 | "files": [ 31 | "lib" 32 | ], 33 | "dependencies": { 34 | "html-tags": "^1.1.1", 35 | "object-assign": "^4.0.1", 36 | "parse-css-class-id-selector": "^1.0.0" 37 | }, 38 | "devDependencies": { 39 | "ava": "^0.16.0", 40 | "babel-cli": "^6.4.0", 41 | "babel-preset-es2015": "^6.3.13", 42 | "babel-register": "^6.4.3", 43 | "coveralls": "^2.11.6", 44 | "cz-conventional-changelog": "^1.1.5", 45 | "deku": "^2.0.0-rc16", 46 | "eslint": "^3.10.0", 47 | "eslint-config-dustinspecker": "^1.0.0", 48 | "eslint-path-formatter": "^0.1.1", 49 | "eslint-plugin-no-use-extend-native": "^0.3.1", 50 | "eslint-plugin-xo": "^1.0.0", 51 | "nyc": "^8.4.0", 52 | "react": "^15.0.0", 53 | "semantic-release": "^4.3.5" 54 | }, 55 | "ava": { 56 | "files": [ 57 | "tests/test.js" 58 | ], 59 | "require": [ 60 | "babel-register" 61 | ] 62 | }, 63 | "nyc": { 64 | "exclude": [ 65 | "node_modules", 66 | "tests" 67 | ] 68 | }, 69 | "config": { 70 | "commitizen": { 71 | "path": "./node_modules/cz-conventional-changelog" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # dscript 2 | [![NPM version](https://badge.fury.io/js/dscript.svg)](https://badge.fury.io/js/dscript) 3 | [![Build Status](https://travis-ci.org/dustinspecker/dscript.svg)](https://travis-ci.org/dustinspecker/dscript) 4 | [![Coverage Status](https://img.shields.io/coveralls/dustinspecker/dscript.svg)](https://coveralls.io/r/dustinspecker/dscript?branch=master) 5 | 6 | [![Code Climate](https://codeclimate.com/github/dustinspecker/dscript/badges/gpa.svg)](https://codeclimate.com/github/dustinspecker/dscript) 7 | [![Dependencies](https://david-dm.org/dustinspecker/dscript.svg)](https://david-dm.org/dustinspecker/dscript/#info=dependencies&view=table) 8 | [![DevDependencies](https://david-dm.org/dustinspecker/dscript/dev-status.svg)](https://david-dm.org/dustinspecker/dscript/#info=devDependencies&view=table) 9 | 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 11 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 12 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 13 | 14 | > Framework agnostic hyperscript 15 | 16 | > Should work with any JSX pragma that works out of the box with Babel's JSX implementation or a function that accepts an HTML tag or component, attributes object, and children list. 17 | 18 | ## Install 19 | ``` 20 | npm install --save dscript 21 | ``` 22 | 23 | **Note: Webpack users will need to setup a [json-loader](https://github.com/webpack/json-loader) as dscript relies on [html-tags](https://github.com/sindresorhus/html-tags), which uses a [JSON file](https://github.com/sindresorhus/html-tags/blob/master/html-tags.json)** 24 | 25 | ## General Usage 26 | ```javascript 27 | import dscript from 'dscript' 28 | import {element} from 'deku' 29 | 30 | const {div, li, ul} = dscript(element) 31 | 32 | const handleClick = () => alert('hi!') 33 | 34 | export default ({props}) => 35 | div('.list-container', {onClick: handleClick}, [ 36 | ul( 37 | props.items.map(item => 38 | li(item.name) 39 | ) 40 | ) 41 | ]) 42 | ``` 43 | 44 | ## Usage with React 45 | **It is recommended to use [dscript-react](https://github.com/dustinspecker/dscript-react) to remove dscript boilerplate.** 46 | 47 | Take the following: 48 | ```javascript 49 | import React from 'react' 50 | 51 | export default props => 52 | 57 | ``` 58 | or: 59 | ```javascript 60 | import {createElement} from 'react' 61 | 62 | export default props => 63 | createElement('ul', {}, 64 | props.items.map(item => 65 | createElement('li', {}, [ 66 | item.name 67 | ]) 68 | ) 69 | ) 70 | ``` 71 | 72 | and instead write: 73 | 74 | ```javascript 75 | import {createElement} from 'react' 76 | import dscript from 'dscript' 77 | 78 | const {li, ul} = dscript(createElement) 79 | 80 | export default props => 81 | ul( 82 | props.items.map(item => 83 | li(item.name) 84 | ) 85 | ) 86 | 87 | ``` 88 | 89 | ## Usage with Deku 90 | **It is recommended to use [dscript-deku](https://github.com/dustinspecker/dscript-deku) to remove dscript boilerplate.** 91 | 92 | Take the following: 93 | ```javascript 94 | import {element} from 'deku' 95 | 96 | export default ({props}) => 97 | 102 | ``` 103 | 104 | or: 105 | 106 | ```javascript 107 | import {element} from 'deku' 108 | 109 | export default ({props}) => 110 | element('ul', {}, 111 | props.items.map(item => 112 | element('li', {}, [ 113 | item.name 114 | ]) 115 | ) 116 | ) 117 | ``` 118 | 119 | and instead write: 120 | ```javascript 121 | import dscript from 'dscript' 122 | import {element} from 'deku' 123 | 124 | const {li, ul} = dscript(element) 125 | 126 | export default ({props}) => 127 | ul( 128 | props.items.map(item => 129 | li(item.name) 130 | ) 131 | ) 132 | ``` 133 | 134 | ## Usage with Custom Components 135 | Custom components example is shown using React, but works with any framework that works with dscript's basic functionality. 136 | 137 | ```javascript 138 | import dscript from 'dscript' 139 | import {createElement} from 'react' 140 | 141 | import customComponent from './custom-component' 142 | 143 | const creator = dscript(createElement) 144 | 145 | const {div, li, ul} = creator 146 | const customComponentCreator = creator(customComponent) 147 | 148 | const handleClick = () => alert('hi!') 149 | 150 | export default props => 151 | div('.list-container', {onClick: handleClick}, [ 152 | customComponentCreator({total: props.total}), 153 | ul( 154 | props.items.map(item => 155 | li(item.name) 156 | ) 157 | ) 158 | ]) 159 | ``` 160 | 161 | ## API 162 | ### dscript(createElement) 163 | Returns an object with properties consisting of HTML tags with values being [creator functions](#creator-functions). 164 | 165 | #### createElement 166 | type: `function` 167 | 168 | A function to use to create the Virtual DOM. For example, React's `createElement` or Deku's `element`. 169 | 170 | ### dscript(createElement)(customComponent) 171 | 172 | Returns a [creator function](#creator-functions) to be used in dscript. 173 | 174 | For example: 175 | 176 | ```javascript 177 | import {createElement} from 'react' 178 | import customComponent from './lib/custom-react-component/' 179 | import dscript from 'dscript' 180 | 181 | const creator = dscript(createElement) 182 | 183 | const {div} = creator 184 | const custom = creator(customComponent) 185 | 186 | export default div([custom()]) 187 | ``` 188 | 189 | #### createElement 190 | 191 | Same as above 192 | 193 | #### customComponent 194 | 195 | type: `any` 196 | 197 | Should be a valid component for the `createElement` function. 198 | 199 | 200 | ### Creator Functions 201 | `creatorFunction([cssClassesAndOrIdSelector,] [attributes,] [children])` 202 | 203 | A function that returns a virtual DOM node created with `createElement`. 204 | 205 | #### cssClassesAndOrIdSelector 206 | type: `string` 207 | 208 | default: `null` 209 | 210 | A convenience to add class names and an id to a virtual DOM node. **Note: The provided selector will override `attribute`'s class and id.** 211 | 212 | ### attributes 213 | type: `object` 214 | 215 | default: `{}` 216 | 217 | An object that will be passed as the attributes to the virutal DOM node. 218 | 219 | ### children 220 | type: `...Elements` 221 | 222 | default: `[]` 223 | 224 | The list of children passed to the created virtual DOM node. 225 | 226 | ## LICENSE 227 | MIT © [Dustin Specker](https://github.com/dustinspecker) 228 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import htmlTags from 'html-tags' 3 | import objectAssign from 'object-assign' 4 | import parseCssClassIdSelector from 'parse-css-class-id-selector' 5 | 6 | /** 7 | * Retrieve class names and id from a selector 8 | * @param {String} selector - CSS class and/or id selector to parse 9 | * @return {Object} - object with class and id properties, if found in selector 10 | * {String} class 11 | * {String} id 12 | */ 13 | const getClassesAndId = selector => { 14 | const {classNames, id} = parseCssClassIdSelector(selector) 15 | 16 | const classesAndId = {} 17 | 18 | if (classNames.length) { 19 | classesAndId.class = classNames.join(' ') 20 | } 21 | 22 | if (id) { 23 | classesAndId.id = id 24 | } 25 | 26 | return classesAndId 27 | } 28 | 29 | /** 30 | * Return a creator function with properties having HTML tag names with values 31 | * being creator functions for the respective HTML element. 32 | * 33 | * The stand-alone returned creator function is meant to be used with custom 34 | * components 35 | * 36 | * @param {Function} createElement - function producing virtual nodes - typically the function used for JSX 37 | * @return {Function} - explained above 38 | */ 39 | module.exports = createElement => { 40 | /* eslint-disable complexity */ 41 | if (typeof createElement !== 'function') { 42 | throw new TypeError('Expected createElement to be a function') 43 | } 44 | 45 | const creator = tagOrComponent => (classesAndId, attrs, ...children) => { 46 | let attrsToPass = attrs || {} 47 | , childrenToPass = children || [] 48 | 49 | if (Array.isArray(attrsToPass)) { 50 | // case: div('.hello', ['hi']) 51 | childrenToPass = attrsToPass 52 | attrsToPass = {} 53 | } else if (typeof attrsToPass !== 'object') { 54 | // case: div('.hello', 7) 55 | childrenToPass = [attrsToPass] 56 | attrsToPass = {} 57 | } 58 | 59 | if (Array.isArray(classesAndId)) { 60 | // case: div(['hi']) 61 | childrenToPass = classesAndId 62 | } else if (typeof classesAndId === 'object') { 63 | // case: div({tabindex: 4}) 64 | attrsToPass = classesAndId 65 | } 66 | 67 | if (typeof classesAndId === 'string' && (classesAndId.indexOf('.') === 0 || classesAndId.indexOf('#') === 0)) { 68 | // case: div('.hello') 69 | objectAssign(attrsToPass, getClassesAndId(classesAndId)) 70 | } else if (classesAndId !== undefined && typeof classesAndId !== 'object') { 71 | // case: div(2342374) 72 | childrenToPass = [classesAndId] 73 | } 74 | 75 | return createElement(tagOrComponent, attrsToPass, ...childrenToPass) 76 | } 77 | 78 | // attach each HTML creator function to a creator function for custom components 79 | return htmlTags.reduce((acc, tag) => { 80 | acc[tag] = creator(tag) 81 | 82 | return acc 83 | }, creator) 84 | } 85 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import {createElement} from 'react' 3 | import {element} from 'deku' 4 | import test from 'ava' 5 | 6 | import dscript from '../lib/' 7 | 8 | test('throws when createElement is not a function', t => { 9 | t.throws(dscript, TypeError) 10 | t.throws(dscript, /Expected createElement to be a function/) 11 | }) 12 | 13 | test('returns an object with html tag names that execute createElement', t => { 14 | let aCalled = false 15 | , buttonCalled = false 16 | 17 | const {a, button} = dscript(tagName => { 18 | if (tagName === 'a') { 19 | aCalled = true 20 | } 21 | 22 | if (tagName === 'button') { 23 | buttonCalled = true 24 | } 25 | }) 26 | 27 | a() 28 | button() 29 | 30 | t.truthy(aCalled) 31 | t.truthy(buttonCalled) 32 | }) 33 | 34 | test('dscript fns pass attributes object', t => { 35 | let divCalled = false 36 | , pCalled = false 37 | 38 | const divAttrs = { 39 | id: 'hello' 40 | } 41 | 42 | const pAttrs = { 43 | class: 'goodbye' 44 | } 45 | 46 | const {div, p} = dscript((tagName, attrs, ...children) => { 47 | if (tagName === 'div' && attrs === divAttrs) { 48 | t.deepEqual(children, []) 49 | divCalled = true 50 | } 51 | 52 | if (tagName === 'p' && attrs === pAttrs) { 53 | t.deepEqual(children, []) 54 | pCalled = true 55 | } 56 | }) 57 | 58 | div(divAttrs) 59 | p(pAttrs) 60 | 61 | t.truthy(divCalled) 62 | t.truthy(pCalled) 63 | }) 64 | 65 | test('dscript fns pass empty object by default for attributes', t => { 66 | let inputCalled = false 67 | 68 | const {input} = dscript((tagName, attrs) => { 69 | if (tagName === 'input') { 70 | t.deepEqual(attrs, {}) 71 | inputCalled = true 72 | } 73 | }) 74 | 75 | input() 76 | 77 | t.truthy(inputCalled) 78 | }) 79 | 80 | test('dscript fns pass children array', t => { 81 | let bCalled = false 82 | , videoCalled = false 83 | 84 | const bChildren = ['yo'] 85 | const videoChildren = ['hello'] 86 | 87 | const {b, video} = dscript((tagName, attrs, ...children) => { 88 | if (tagName === 'b') { 89 | t.deepEqual(children, bChildren) 90 | bCalled = true 91 | } 92 | 93 | if (tagName === 'video') { 94 | t.deepEqual(children, videoChildren) 95 | videoCalled = true 96 | } 97 | }) 98 | 99 | b({}, bChildren) 100 | video({}, videoChildren) 101 | 102 | t.truthy(bCalled) 103 | t.truthy(videoCalled) 104 | }) 105 | 106 | test('dscript fns pass empty children array by default', t => { 107 | let spanCalled = false 108 | 109 | const {span} = dscript((tagName, attrs, ...children) => { 110 | if (tagName === 'span') { 111 | t.deepEqual(children, []) 112 | spanCalled = true 113 | } 114 | }) 115 | 116 | span() 117 | 118 | t.truthy(spanCalled) 119 | }) 120 | 121 | test('it passes single child when class and attrs are provided', t => { 122 | let spanCalled = false 123 | 124 | const {span} = dscript((tagName, attrs, ...children) => { 125 | if (tagName === 'span') { 126 | t.deepEqual(children, [789]) 127 | spanCalled = true 128 | } 129 | }) 130 | 131 | span('.hello', {}, 789) 132 | 133 | t.truthy(spanCalled) 134 | }) 135 | 136 | test('it passes single child when class is provied', t => { 137 | let spanCalled = false 138 | 139 | const {span} = dscript((tagName, attrs, ...children) => { 140 | if (tagName === 'span') { 141 | t.deepEqual(children, [789]) 142 | spanCalled = true 143 | } 144 | }) 145 | 146 | span('.hello', 789) 147 | 148 | t.truthy(spanCalled) 149 | }) 150 | 151 | test('it passes single child when only non-string child is passed', t => { 152 | let spanCalled = false 153 | 154 | const {span} = dscript((tagName, attrs, ...children) => { 155 | if (tagName === 'span') { 156 | t.deepEqual(children, [789]) 157 | spanCalled = true 158 | } 159 | }) 160 | 161 | span(789) 162 | 163 | t.truthy(spanCalled) 164 | }) 165 | 166 | test('it passes single child when only string child is passed', t => { 167 | let spanCalled = false 168 | 169 | const {span} = dscript((tagName, attrs, ...children) => { 170 | if (tagName === 'span') { 171 | t.deepEqual(children, ['hello']) 172 | spanCalled = true 173 | } 174 | }) 175 | 176 | span('hello') 177 | 178 | t.truthy(spanCalled) 179 | }) 180 | 181 | test('dscript fn can handle no attrs, but selector and chilren', t => { 182 | let spanCalled = false 183 | 184 | const {span} = dscript((tagName, attrs, ...children) => { 185 | if (tagName === 'span') { 186 | t.is(attrs.class, 'hi') 187 | t.deepEqual(children, ['yo']) 188 | spanCalled = true 189 | } 190 | }) 191 | 192 | span('.hi', ['yo']) 193 | 194 | t.truthy(spanCalled) 195 | }) 196 | 197 | test('dscript fns pass empty object if attrs is not passed but children is', t => { 198 | let divCalled = false 199 | 200 | const divChildren = ['hi'] 201 | 202 | const {div} = dscript((tagName, attrs, ...children) => { 203 | if (tagName === 'div') { 204 | t.deepEqual(attrs, {}) 205 | t.deepEqual(children, divChildren) 206 | divCalled = true 207 | } 208 | }) 209 | 210 | div(divChildren) 211 | 212 | t.truthy(divCalled) 213 | }) 214 | 215 | test('dscript is a fn that accepts a list of non html tags to pass to createElement', t => { 216 | let fancyCalled = false 217 | 218 | const fancy = dscript(tagName => { 219 | if (tagName === 'fancy') { 220 | fancyCalled = true 221 | } 222 | })('fancy') 223 | 224 | fancy() 225 | 226 | t.truthy(fancyCalled) 227 | }) 228 | 229 | test('dscript attaches optional classes and id to attributes and overrides provided attrs', t => { 230 | let divCalled = false 231 | 232 | const {div} = dscript((tagName, attrs, ...children) => { 233 | if (tagName === 'div') { 234 | t.is(attrs.id, 'world') 235 | t.deepEqual(children, []) 236 | divCalled = true 237 | } 238 | }) 239 | 240 | div('.hello#world.good-bye', {class: 'yo', id: '3'}) 241 | 242 | t.truthy(divCalled) 243 | }) 244 | 245 | test('id selector overrides attrs.id if provided', t => { 246 | let divCalled = false 247 | 248 | const {div} = dscript((tagName, attrs) => { 249 | if (tagName === 'div') { 250 | t.is(attrs.id, 'yo') 251 | t.is(attrs.class, 'hi') 252 | divCalled = true 253 | } 254 | }) 255 | 256 | div('#yo', {class: 'hi', id: '3'}) 257 | 258 | t.truthy(divCalled) 259 | }) 260 | 261 | test('class selector overrides attrs.clas if provided', t => { 262 | let divCalled = false 263 | 264 | const {div} = dscript((tagName, attrs) => { 265 | if (tagName === 'div') { 266 | t.is(attrs.id, '3') 267 | t.is(attrs.class, 'yo') 268 | divCalled = true 269 | } 270 | }) 271 | 272 | div('.yo', {class: 'hi', id: '3'}) 273 | 274 | t.truthy(divCalled) 275 | }) 276 | 277 | test('verify it works with Deku\'s element', t => { 278 | const {div} = dscript(element) 279 | 280 | const dekuDiv = div('.yo#hi', {title: 'hello'}, ['world']) 281 | 282 | t.is(dekuDiv.type, 'div') 283 | t.deepEqual(dekuDiv.attributes, {title: 'hello', class: 'yo', id: 'hi'}) 284 | t.deepEqual(dekuDiv.children[0], {type: '#text', nodeValue: 'world'}) 285 | }) 286 | 287 | test('verify it works with React\'s createElement', t => { 288 | const {div} = dscript(createElement) 289 | 290 | const reactDiv = div('.yo#hi', {title: 'hello'}, ['world']) 291 | 292 | t.is(reactDiv.type, 'div') 293 | t.is(reactDiv.props.title, 'hello') 294 | t.is(reactDiv.props.id, 'hi') 295 | t.is(reactDiv.props.class, 'yo') 296 | t.deepEqual(reactDiv.props.children[0], 'world') 297 | }) 298 | --------------------------------------------------------------------------------