├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── dist ├── hyperapp-context.js └── hyperapp-context.js.map ├── package-lock.json ├── package.json ├── src ├── decorator.js ├── index.js ├── nestable.js ├── processor.js ├── resolve.js └── with.js └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | 5 | env: 6 | - NODE_ENV=development 7 | 8 | install: 9 | - npm install 10 | 11 | script: 12 | - npm test 13 | 14 | notifications: 15 | email: false -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zacharias Enochsson 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is only compatible with hyperapp v1, and I do not intend to maintain it any longer. Hence, I am archiving it. 2 | 3 | # hyperapp-context 4 | 5 | [![Travis CI](https://api.travis-ci.org/zaceno/hyperapp-context.svg?branch=master)](https://travis-ci.org/zaceno/hyperapp-context) [![npm](https://img.shields.io/npm/v/hyperapp-context.svg)](https://www.npmjs.org/package/hyperapp-context) 6 | 7 | In [Hyperapp](https://hyperapp.js.org), the way to provide data to components is by passing properties to them. If your component tree is deep, and finely separated, this can quickly become repetetitive and burdensome -- and may lead to hard-to-find bugs. 8 | 9 | "Contexts" offer a *complementary* (not exclusive) way of providing data to components which can remedy the situation. 10 | 11 | This "higher-order-app" (or app decorator) enables the use of context in Hyperapp-apps. 12 | 13 | ## Installation 14 | 15 | If you're using a module bundler, install With npm or Yarn: 16 | 17 |
 18 | npm i hyperapp-context
 19 | 
20 | 21 | And then import the same way you would anything else, in the same file where you import `app` from Hyperapp 22 | 23 | ```js 24 | import {h, app as _app} from "hyperapp" 25 | import context from "hyperapp-context" 26 | ``` 27 | 28 | Alternatively, if you're not using a module bunder, you can download hyperapp-context from a CDN like [unpkg.com](https://unpkg.com/hyperapp-context) and it will be globally available as window.context. 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | ## Usage 42 | 43 | ### Enable context 44 | 45 | In order to enable the context system in your app, don't call Hyperapp's `app` directly. First, wrap it with `withContext`. 46 | 47 | ```js 48 | import {app as _app} from 'hyperapp' 49 | import {withContext} from 'hyperapp-context' 50 | const app = withContext(_app) 51 | 52 | //... 53 | 54 | app(state, actions, view, container) 55 | ``` 56 | 57 | ### Access context in components 58 | 59 | The "context" is a set of data available to components without the need to pass it as props from its parent-container. In order to access context-data, define your components using this signature: 60 | 61 | ```jsx 62 | const MyComponent = (props, children) => context => ( 63 |
{context.bar} 64 | ) 65 | ``` 66 | 67 | ### Write to the context 68 | 69 | In order for a component to have access to data in the context, it must first have been written to the context, by a component higher up in the component-tree. A component can write to the context using the function provided as the second argument after the `context`. 70 | 71 | ```jsx 72 | const GrandDadComponent = (props, children) => (context, setContext) => { 73 | setContext({ 74 | foo: 'foo', 75 | bar: 'bar', 76 | }) 77 | return ( 78 |
79 | ... 80 |
81 | ) 82 | } 83 | ``` 84 | 85 | The example makes `foo` and `bar` available to any decendant in the component tree. 86 | 87 | If any components even further up the tree had already defined `foo` or `bar`, this would override those values for any decendants of `GrandDadComponent`, but *siblings* would recieve the original valuues. 88 | 89 | #### Define context directly in the view 90 | 91 | You can write to the context for your entire app, by setting it in your view. A common use for this is to make `state` and `actions` available to all components. 92 | 93 | ```js 94 | 95 | const view = (state, actions) => (context, setContext) => { 96 | setContext({state, actions}) 97 | return ( 98 |
99 | ... 100 |
101 | ) 102 | } 103 | 104 | ``` 105 | 106 | #### Use a component to set context 107 | 108 | A technical limitation with the `setContext` function described above, is that it should really only be called once in a component. If called multiple times, only the last call will have an effect. 109 | 110 | If you would like to apply different contexts to different branches of descendants in a component, first define a component that can be used to set the context: 111 | 112 | ```js 113 | const SetContext = (props, children) => (_, setContext) => { 114 | setContext(props) 115 | return children 116 | } 117 | 118 | ``` 119 | 120 | Now you may use this to define different contexts for different branches inside a single component/view: 121 | 122 | ```jsx 123 | const view = (state, actions) => ( 124 | 125 |
126 |
127 | 128 | 129 | 130 | 131 |
132 |
133 | 134 |
135 | 136 |
137 |
138 |
139 | ) 140 | ``` 141 | 142 | 143 | ### Example 144 | 145 | This [TodoMVC example](https://codepen.io/zaceno/pen/gvGgQP?editors=0010) makes liberal (extreme, perhaps...) use of context. 146 | 147 | ## Nestable 148 | 149 | Embed hyperapp-apps in your main app, as if they were components. 150 | 151 | Usage is exactly as in https://github.com/zaceno/hyperapp-nestable, except for these two details: 152 | 153 | Import using: 154 | 155 | ```js 156 | import {nestable} from 'hyperapp-context' 157 | ``` 158 | 159 | These nestables are *context enabled*, meaning you get access to the external context in the nestable's view, and can share data from the component with its children, via context: 160 | 161 | ```js 162 | const MyComponent = nestable( 163 | //State 164 | {...}, 165 | 166 | //Actions 167 | {...}, 168 | 169 | //View 170 | (state, actions) => (props, children) => (context, setContext) => { 171 | setContext({...}) 172 | return children 173 | } 174 | ) 175 | ``` 176 | 177 | ## Preprocessing the VDOM 178 | 179 | Sometimes you want a component which does not itself add anything to the virtual-dom-tree, but simply modifies it's children in some way, for example, by attaching dom handlers. For this purpose, we export `processor` 180 | 181 | ```jsx 182 | import {processor} from 'hyperapp-context' 183 | 184 | const MyProcessor = processor((props, children, context) => { 185 | /* 186 | Here, props will be {foo: 'bar'} 187 | children will be {nodeName: 'p', attributes: {}, children: ['bop']} 188 | return whatever you want to make of this. 189 | */ 190 | }) 191 | 192 | const Child = _ => context =>

bop

193 | 194 | ... 195 | 196 | ... 197 | 198 | ``` 199 | 200 | ### Decorators 201 | 202 | Working directly with vnodes as in the example above is rarely necessary, and a bit rough. Most often what you 203 | want to do is simply to add a few event/lifecycle handers or perhaps a class to the child nodes. To facilitate 204 | this we export `decorator` which lets you define a processing component that does just that: 205 | 206 | ```jsx 207 | const SelectionDecorator = decorator(({row, col}, {selection}) => ({ 208 | onmousedown: ev => { 209 | ev.preventDefault(true) 210 | selection.start({row, col}) 211 | }, 212 | onmouseup: ev => { 213 | ev.preventDefault(true) 214 | selection.end({row, col}) 215 | }, 216 | onmouseover: ev => { 217 | ev.preventDefault(true) 218 | selection.select({row, col}) 219 | }, 220 | class: selection.isSelected({row, col}) && 'selected' 221 | })) 222 | 223 | //... 224 | 225 | 226 | {values[i][j]} 227 | 228 | 229 | //... 230 | ``` 231 | 232 | ## Example 233 | 234 | This example demonstrates using a nestable as a provider of selection state and actions via the context, and decorating table cells with selection event handlers, so that you can operate on the main state using the selection 235 | data. 236 | 237 | https://codepen.io/zaceno/pen/vdwQdy?editors=0110 238 | 239 | ## License 240 | 241 | hyperapp-context is MIT licensed. See [LICENSE.md](./LICENSE.md). 242 | -------------------------------------------------------------------------------- /dist/hyperapp-context.js: -------------------------------------------------------------------------------- 1 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("hyperapp")):"function"==typeof define&&define.amd?define(["exports","hyperapp"],n):n(t.context={},t.hyperapp)}(this,function(t,n){"use strict";function e(t,n){return n=n||{},null==t?t:"function"==typeof t?e(t(n,function(t){n=Object.assign({},n,t)}),n):Array.isArray(t)?(t=t.map(function(t){return e(t,n)}),t=(t=Array.prototype.concat.apply([],t)).filter(function(t){return null!=t})):t.attributes?{nodeName:t.nodeName,attributes:Object.assign({},t.attributes),children:e(t.children,n)}:t}function r(t){return function(n,r){return function(o){return t(n,e(r),o)}}}t.withContext=function(t){return function(n,r,o,u){return t(n,r,function(t,n){var r=e(o(t,n));return r&&(r.length?r[0]:r)},u)}},t.processor=r,t.decorator=function(t){return r(function(n,e,r){var o=t(n,r);return e.map(function(t){return t.attributes?(Object.keys(o).forEach(function(n){if("class"===n){var e=t.attributes.class,r=o.class;t.attributes.class=(e?e+" ":"")+(r||"")}else if("on"===n.substr(0,2)){var u=t.attributes[n],i=o[n];t.attributes[n]=u?function(t,n){u(t,n),i(t,n)}:i}else t.attributes[n]=o[n]}),t):t})})},t.nestable=function(t,r,o,u){return r._$r=function(){return{}},function(i,c){return function(a){return n.h(u||"x-",{key:i.key,id:i.id,class:i.class,oncreate:function(u){u._$p=i,u._$c=c,u._$x=a;var f=n.app(t,r,function(t,n){var r=o(t,n);return"function"==typeof r&&(r=r(u._$p,u._$c)),(r=e(r,u._$x))&&r.length?r[0]:r},u);u._$r=f._$r,u._$u=f.uninit,f.init&&f.init(i),i.oncreate&&i.oncreate(u)},onupdate:function(t){t._$p=i,t._$c=c,t._$x=a,t._$r(),i.onupdate&&i.onupdate(t)},ondestroy:function(t){t._$u&&t._$u(),i.ondestroy&&i.ondestroy(t)},onremove:function(t,n){if(!i.onremove)return n();i.onremove(t,n)}})}}},Object.defineProperty(t,"__esModule",{value:!0})}); 2 | //# sourceMappingURL=hyperapp-context.js.map -------------------------------------------------------------------------------- /dist/hyperapp-context.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["dist/hyperapp-context.js"],"names":["global","factory","exports","module","require","define","amd","context","hyperapp","this","resolveNode","node","props","Object","assign","Array","isArray","map","n","prototype","concat","apply","filter","attributes","nodeName","children","treeProcessor","processor","withContext","app$$1","initialState","actionDefinitions","originalView","container","state","actions","length","decorator","getDecoration","decoration","child","keys","forEach","name","oc","class","dc","substr","oh","dh","a","b","nestable","view","tagname","_$r","h","key","id","oncreate","el","_$p","_$c","_$x","wired","app","s","_$u","uninit","init","onupdate","ondestroy","onremove","done","defineProperty","value"],"mappings":"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","sourcesContent":["(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('hyperapp')) :\n\ttypeof define === 'function' && define.amd ? define(['exports', 'hyperapp'], factory) :\n\t(factory((global.context = {}),global.hyperapp));\n}(this, (function (exports,hyperapp) { 'use strict';\n\nfunction resolveNode (node, context) {\n context = context || {};\n if (node == null) return node\n if (typeof node === 'function') {\n return resolveNode(\n node(\n context,\n function (props) {\n context = Object.assign({}, context, props);\n }\n ),\n context\n )\n }\n if (Array.isArray(node)) {\n node = node.map(function (n) { return resolveNode(n, context)});\n node = Array.prototype.concat.apply([], node);\n node = node.filter(function (n) { return n != null });\n return node\n }\n if (!node.attributes) return node\n return {\n nodeName: node.nodeName,\n attributes: Object.assign({}, node.attributes),\n children: resolveNode(node.children, context)\n }\n}\n\nfunction treeProcessor (processor) {\n return function (props, children) {\n return function (context) {\n return processor(props, resolveNode(children), context)\n }\n }\n}\n\nfunction decorator (getDecoration) {\n return treeProcessor(function (props, children, context){\n var decoration = getDecoration(props, context);\n return children.map(function (child) {\n if (!child.attributes) return child\n Object.keys(decoration).forEach(function (name) {\n if (name === 'class') {\n var oc = child.attributes.class;\n var dc = decoration.class;\n child.attributes.class = (oc ? oc + ' ' : '') + (dc ? dc : '');\n } else if (name.substr(0, 2) === 'on') {\n var oh = child.attributes[name];\n var dh = decoration[name];\n child.attributes[name] = !oh ? dh : function (a,b) {oh(a, b); dh(a, b);};\n } else {\n child.attributes[name] = decoration[name];\n }\n });\n return child \n })\n })\n}\n\nfunction nestable (state, actions, view, tagname) {\n actions._$r = function () {return {}};\n return function (props, children) {\n return function (context) {\n return hyperapp.h(tagname || 'x-', {\n key: props.key,\n id: props.id,\n class: props.class,\n oncreate: function (el) {\n el._$p = props;\n el._$c = children;\n el._$x = context;\n var wired = hyperapp.app(\n state,\n actions,\n function (s, a) {\n var node = view(s, a);\n if (typeof node === 'function') node = node(el._$p, el._$c);\n node = resolveNode(node, el._$x);\n return (node && node.length) ? node[0] : node\n },\n el\n );\n el._$r = wired._$r;\n el._$u = wired.uninit;\n wired.init && wired.init(props);\n props.oncreate && props.oncreate(el);\n },\n onupdate: function (el) {\n el._$p = props;\n el._$c = children;\n el._$x = context;\n el._$r();\n props.onupdate && props.onupdate(el);\n },\n ondestroy: function (el) {\n el._$u && el._$u();\n props.ondestroy && props.ondestroy(el);\n },\n onremove: function (el, done) {\n if (!props.onremove) return done()\n \n props.onremove(el, done);\n }\n }) \n }\n }\n}\n\nfunction _with (app$$1) {\n return function (initialState, actionDefinitions, originalView, container) {\n var view = function (state, actions) {\n var node = resolveNode(originalView(state, actions));\n return node && (node.length ? node[0] : node)\n };\n return app$$1(initialState, actionDefinitions, view, container)\n }\n}\n\nexports.withContext = _with;\nexports.processor = treeProcessor;\nexports.decorator = decorator;\nexports.nestable = nestable;\n\nObject.defineProperty(exports, '__esModule', { value: true });\n\n})));\n//# sourceMappingURL=hyperapp-context.js.map\n"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperapp-context", 3 | "version": "1.1.0", 4 | "description": "Context-aware Hyperapp components", 5 | "main": "dist/hyperapp-context.js", 6 | "module": "src/index.js", 7 | "scripts": { 8 | "umd": "rollup src/index.js -f umd -n context -m -o dist/hyperapp-context.js", 9 | "minify": "uglifyjs dist/hyperapp-context.js -o dist/hyperapp-context.js -mc --source-map includeSources,url=hyperapp-context.js.map", 10 | "build": "npm run umd && npm run minify", 11 | "test": "npm run build && ava test/*.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/zaceno/hyperapp-context.git" 16 | }, 17 | "author": "Zacharias Enochsson", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/zaceno/hyperapp-context/issues" 21 | }, 22 | "homepage": "https://github.com/zaceno/hyperapp-context#readme", 23 | "peerDependencies": { 24 | "hyperapp": "1.x.x" 25 | }, 26 | "devDependencies": { 27 | "ava": "^1.0.0-beta.3", 28 | "hyperapp": "1.x.x", 29 | "jsdom": "^11.6.2", 30 | "rollup": "^0.52.2", 31 | "uglify-js": "^3.2.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/decorator.js: -------------------------------------------------------------------------------- 1 | import treeProcessor from './processor' 2 | 3 | export default function (getDecoration) { 4 | return treeProcessor(function (props, children, context){ 5 | var decoration = getDecoration(props, context) 6 | return children.map(function (child) { 7 | if (!child.attributes) return child 8 | Object.keys(decoration).forEach(function (name) { 9 | if (name === 'class') { 10 | var oc = child.attributes.class 11 | var dc = decoration.class 12 | child.attributes.class = (oc ? oc + ' ' : '') + (dc ? dc : '') 13 | } else if (name.substr(0, 2) === 'on') { 14 | var oh = child.attributes[name] 15 | var dh = decoration[name] 16 | child.attributes[name] = !oh ? dh : function (a,b) {oh(a, b); dh(a, b)} 17 | } else { 18 | child.attributes[name] = decoration[name] 19 | } 20 | }) 21 | return child 22 | }) 23 | }) 24 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import decorator from './decorator' 2 | import processor from './processor' 3 | import nestable from './nestable' 4 | import withContext from './with' 5 | 6 | export { 7 | withContext, 8 | processor, 9 | decorator, 10 | nestable 11 | } -------------------------------------------------------------------------------- /src/nestable.js: -------------------------------------------------------------------------------- 1 | import {h, app} from 'hyperapp' 2 | import resolveNode from './resolve' 3 | export default function (state, actions, view, tagname) { 4 | actions._$r = function () {return {}} 5 | return function (props, children) { 6 | return function (context) { 7 | return h(tagname || 'x-', { 8 | key: props.key, 9 | id: props.id, 10 | class: props.class, 11 | oncreate: function (el) { 12 | el._$p = props 13 | el._$c = children 14 | el._$x = context 15 | var wired = app( 16 | state, 17 | actions, 18 | function (s, a) { 19 | var node = view(s, a) 20 | if (typeof node === 'function') node = node(el._$p, el._$c) 21 | node = resolveNode(node, el._$x) 22 | return (node && node.length) ? node[0] : node 23 | }, 24 | el 25 | ) 26 | el._$r = wired._$r 27 | el._$u = wired.uninit 28 | wired.init && wired.init(props) 29 | props.oncreate && props.oncreate(el) 30 | }, 31 | onupdate: function (el) { 32 | el._$p = props 33 | el._$c = children 34 | el._$x = context 35 | el._$r() 36 | props.onupdate && props.onupdate(el) 37 | }, 38 | ondestroy: function (el) { 39 | el._$u && el._$u() 40 | props.ondestroy && props.ondestroy(el) 41 | }, 42 | onremove: function (el, done) { 43 | if (!props.onremove) return done() 44 | 45 | props.onremove(el, done) 46 | } 47 | }) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/processor.js: -------------------------------------------------------------------------------- 1 | import resolveNode from './resolve' 2 | 3 | export default function (processor) { 4 | return function (props, children) { 5 | return function (context) { 6 | return processor(props, resolveNode(children), context) 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/resolve.js: -------------------------------------------------------------------------------- 1 | function resolveNode (node, context) { 2 | context = context || {} 3 | if (node == null) return node 4 | if (typeof node === 'function') { 5 | return resolveNode( 6 | node( 7 | context, 8 | function (props) { 9 | context = Object.assign({}, context, props) 10 | } 11 | ), 12 | context 13 | ) 14 | } 15 | if (Array.isArray(node)) { 16 | node = node.map(function (n) { return resolveNode(n, context)}) 17 | node = Array.prototype.concat.apply([], node) 18 | node = node.filter(function (n) { return n != null }) 19 | return node 20 | } 21 | if (!node.attributes) return node 22 | return { 23 | nodeName: node.nodeName, 24 | attributes: Object.assign({}, node.attributes), 25 | children: resolveNode(node.children, context) 26 | } 27 | } 28 | 29 | export default resolveNode 30 | -------------------------------------------------------------------------------- /src/with.js: -------------------------------------------------------------------------------- 1 | import resolveNode from './resolve' 2 | 3 | export default function (app) { 4 | return function (initialState, actionDefinitions, originalView, container) { 5 | var view = function (state, actions) { 6 | var node = resolveNode(originalView(state, actions)) 7 | return node && (node.length ? node[0] : node) 8 | } 9 | return app(initialState, actionDefinitions, view, container) 10 | } 11 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import {JSDOM} from 'jsdom' 2 | const dom = new JSDOM('') 3 | global.window = dom.window 4 | global.document = dom.window.document 5 | 6 | import test from 'ava' 7 | import assert from 'assert' 8 | import {withContext} from '../dist/hyperapp-context' 9 | import {h, app as _app} from 'hyperapp' 10 | const app = withContext(_app) 11 | 12 | function createContainer () { 13 | const el = document.createElement('div') 14 | document.body.appendChild(el) 15 | return el 16 | } 17 | 18 | const Context = (props, children) => (context, setContext) => { 19 | setContext(props) 20 | return children 21 | } 22 | 23 | test.cb('withContext allows context aware components', t => { 24 | const el = createContainer() 25 | const Component = props => context => h('div', {id: 'component'}, ['foo']) 26 | const view = (state, actuins) => h('main', {}, [ h(Component, {}, []) ]) 27 | app({}, {}, view, el) 28 | setTimeout(_ => { 29 | t.is(el.innerHTML, '
foo
') 30 | t.end() 31 | }, 0) 32 | }) 33 | 34 | test.cb('props on Context are available to context-aware descendants', t => { 35 | const el = createContainer() 36 | const Component = _ => ({foo, bar}) => h('span', {id: foo}, [bar]) 37 | const Passthru = _ => h('p', {}, [ h(Component, {}) ]) 38 | const view = _ => h('main', {}, [ 39 | h(Context, {foo: 'foo', bar: 'bar'}, [ 40 | h(Passthru, {}, []) 41 | ]) 42 | ]) 43 | app({}, {}, view, el) 44 | setTimeout(_ => { 45 | t.is(el.innerHTML, '

bar

') 46 | t.end() 47 | },0) 48 | }) 49 | 50 | test.cb('the view can write to context same as components', t => { 51 | const el = createContainer() 52 | const Component = _ => ({foo, bar}) => h('span', {id: foo}, [bar]) 53 | const Passthru = _ => h('p', {}, [ h(Component, {}) ]) 54 | const view = _ => (_, setContext) => { 55 | setContext({foo: 'foo', bar: 'bar'}) 56 | return h(Passthru, {}, []) 57 | } 58 | app({}, {}, view, el) 59 | setTimeout(_ => { 60 | t.is(el.innerHTML, '

bar

') 61 | t.end() 62 | },0) 63 | }) 64 | 65 | test.cb('context-aware components can be nested', t => { 66 | const el = createContainer() 67 | const Passthru1 = _ => h('section', {}, [ h(Component1, {}) ]) 68 | const Component1 = _ => ({foo}) => h('div', {id: foo}, [ h(Passthru2, {}) ]) 69 | const Passthru2 = _ => h('p', {}, [ h(Component2, {}) ]) 70 | const Component2 = _ => ({bar}) => h('span', {}, [ bar ]) 71 | const view = _ => h('main', {}, [ 72 | h(Context, {foo: 'foo', bar: 'bar'}, [ 73 | h(Passthru1, {}, []) 74 | ]) 75 | ]) 76 | app({}, {}, view, el) 77 | setTimeout(_ => { 78 | t.is(el.innerHTML, '

bar

') 79 | t.end() 80 | },0) 81 | }) 82 | 83 | test.cb('Context applies context only within its range', t => { 84 | const el = createContainer() 85 | const Component = _ => ({foo, bar, baz}) => h('span', {}, [foo, bar, baz]) 86 | const view = _ => h(Context, {foo: 'foo', bar: 'bar'}, [ 87 | h('main', {}, [ 88 | h(Context, {bar: 'baz', baz: 'bop'}, [ 89 | h(Component, {}) 90 | ]), 91 | h(Component, {}) 92 | ]) 93 | ]) 94 | app({}, {}, view, el) 95 | setTimeout(_ => { 96 | t.is(el.innerHTML, '
foobazbopfoobar
') 97 | t.end() 98 | }, 0) 99 | }) --------------------------------------------------------------------------------