├── .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 | [](https://travis-ci.org/zaceno/hyperapp-context) [](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 |
{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 |
132 |
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, '
')
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 | })
--------------------------------------------------------------------------------