halfcab 15.0.0 → 15.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import chai from 'chai'
2
2
  import dirtyChai from 'dirty-chai'
3
3
  import sinon from 'sinon'
4
4
  import sinonChai from 'sinon-chai'
5
- import eventEmitter from './index'
5
+ import eventEmitter from './index.mjs'
6
6
  import jsdomGlobal from 'jsdom-global'
7
7
 
8
8
  const {expect} = chai
package/example/app.js ADDED
@@ -0,0 +1,148 @@
1
+ import halfcab, {
2
+ html,
3
+ css,
4
+ injectMarkdown,
5
+ defineRoute,
6
+ gotoRoute,
7
+ updateState,
8
+ formField,
9
+ formIsValid,
10
+ getRouteComponent,
11
+ state,
12
+ nextTick
13
+ } from '../halfcab.mjs'
14
+
15
+ // Some demo styles using the css tag helper
16
+ const styles = css`
17
+ .container {
18
+ max-width: 880px;
19
+ margin: 0 auto;
20
+ }
21
+ .counter {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: 8px;
25
+ padding: 8px 12px;
26
+ border-radius: 8px;
27
+ border: 1px solid #8884;
28
+ }
29
+ .navLink.active {
30
+ font-weight: 600;
31
+ text-decoration: underline;
32
+ }
33
+ .card {
34
+ padding: 12px;
35
+ border: 1px solid #8884;
36
+ border-radius: 8px;
37
+ background: #00000008;
38
+ margin: 12px 0;
39
+ }
40
+ `
41
+
42
+ // Demo components
43
+ const Home = (args) => html`
44
+ <section class="${styles.container}">
45
+ <h2>Welcome to halfcab + lit demo</h2>
46
+ <div class="${styles.card}">
47
+ ${injectMarkdown(`
48
+ ### Features shown
49
+ - Client-side routing
50
+ - Global state + rerender
51
+ - CSS via css helper
52
+ - Form helpers and validation
53
+ - Markdown injection
54
+ `)}
55
+ </div>
56
+
57
+ <div class="${styles.card}">
58
+ <h3>Counter</h3>
59
+ <div class="${styles.counter}">
60
+ <button onclick=${() => updateState({ count: (state.count || 0) - 1 })} aria-label="decrement">–</button>
61
+ <strong>${state.count || 0}</strong>
62
+ <button onclick=${() => updateState({ count: (state.count || 0) + 1 })} aria-label="increment">+</button>
63
+ </div>
64
+ </div>
65
+ </section>
66
+ `
67
+
68
+ const About = () => html`
69
+ <section class="${styles.container}">
70
+ <h2>About</h2>
71
+ <p>This demo page is here to help you quickly test halfcab components and interactions.</p>
72
+ <p>Source: <code>example/</code> directory.</p>
73
+ </section>
74
+ `
75
+
76
+ const Form = () => {
77
+ // A simple form using formField + validation
78
+ state.form = state.form || { name: '', email: '' }
79
+ const bindName = formField(state.form, 'name')
80
+ const bindEmail = formField(state.form, 'email')
81
+ const submit = (e) => {
82
+ e.preventDefault()
83
+ if (formIsValid(state.form)) {
84
+ alert(`Submitted: ${state.form.name} <${state.form.email}>`)
85
+ } else {
86
+ alert('Form is invalid. Please fix errors.')
87
+ }
88
+ }
89
+ return html`
90
+ <section class="${styles.container}">
91
+ <h2>Form</h2>
92
+ <form onsubmit=${submit} novalidate>
93
+ <div class="${styles.card}">
94
+ <label>
95
+ Name
96
+ <input type="text" required placeholder="Ada Lovelace" oninput=${bindName} value="${state.form.name}" />
97
+ </label>
98
+ </div>
99
+ <div class="${styles.card}">
100
+ <label>
101
+ Email
102
+ <input type="email" required placeholder="ada@example.com" oninput=${bindEmail} value="${state.form.email}" />
103
+ </label>
104
+ </div>
105
+ <button type="submit">Submit</button>
106
+ </form>
107
+ </section>
108
+ `
109
+ }
110
+
111
+ // Define routes
112
+ defineRoute({ path: '/', title: 'Home', component: Home })
113
+ defineRoute({ path: '/about', title: 'About', component: About })
114
+ defineRoute({ path: '/form', title: 'Form', component: Form })
115
+
116
+ // Application shell
117
+ const Shell = () => {
118
+ const pathname = (state.router && state.router.pathname) || '/'
119
+ const NavLink = (to, label) => html`
120
+ <a class="navLink ${pathname === to ? 'active' : ''}"
121
+ href="${to}"
122
+ onclick=${(e) => { e.preventDefault(); gotoRoute(to) }}>${label}</a>
123
+ `
124
+
125
+ const Current = getRouteComponent(pathname) || Home
126
+
127
+ return html`
128
+ <header>
129
+ ${NavLink('/', 'Home')}
130
+ ${NavLink('/about', 'About')}
131
+ ${NavLink('/form', 'Form')}
132
+ </header>
133
+ <main>
134
+ ${Current({})}
135
+ </main>
136
+ `
137
+ }
138
+
139
+ // Boot the app
140
+ halfcab({
141
+ el: '#app',
142
+ components: Shell
143
+ }).then(() => {
144
+ // Ensure initial render after possible async state init
145
+ nextTick(() => {})
146
+ }).catch((err) => {
147
+ console.error('Failed to start halfcab demo:', err)
148
+ })
@@ -0,0 +1,50 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>halfcab demo</title>
7
+ <base href="/example/">
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
11
+ "shifty-router": "https://esm.sh/shifty-router@0.1.1",
12
+ "shifty-router/href.js": "https://esm.sh/shifty-router@0.1.1/href.js",
13
+ "shifty-router/history.js": "https://esm.sh/shifty-router@0.1.1/history.js",
14
+ "shifty-router/create-location.js": "https://esm.sh/shifty-router@0.1.1/create-location.js",
15
+
16
+ "lit": "https://esm.sh/lit@3.3.2",
17
+ "@lit-labs/ssr-client": "https://esm.sh/@lit-labs/ssr-client@1.1.8",
18
+ "@lit-labs/ssr": "/example/ssr-browser-stub.js",
19
+
20
+ "axios": "https://esm.sh/axios@0.26.1",
21
+ "csjs-inject": "https://esm.sh/csjs-inject@1.0.1",
22
+ "deepmerge": "https://esm.sh/deepmerge@4.0.0",
23
+ "marked": "https://esm.sh/marked@0.7.0",
24
+ "html-entities": "https://esm.sh/html-entities@2.3.2",
25
+ "qs": "https://esm.sh/qs@6.5.2",
26
+ "nanolru": "https://esm.sh/nanolru@1.0.0",
27
+ "nanocomponent": "https://esm.sh/nanocomponent@6.5.2",
28
+ "deep-object-diff": "https://esm.sh/deep-object-diff@1.1.0",
29
+ "fast-clone": "https://esm.sh/fast-clone@1.5.13",
30
+ "event-emitter": "https://esm.sh/event-emitter@0.3.5"
31
+ }
32
+ }
33
+ </script>
34
+ <style>
35
+ :root { color-scheme: light dark; }
36
+ body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; margin: 0; padding: 0; }
37
+ header { padding: 12px 16px; border-bottom: 1px solid #ccc; display: flex; gap: 12px; align-items: center; }
38
+ header a { text-decoration: none; color: inherit; padding: 6px 10px; border-radius: 6px; }
39
+ header a.active { background: rgba(0,0,0,0.08); }
40
+ main { padding: 16px; }
41
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <!-- Optional initial state for router bootstrapping -->
46
+ <div data-initial="eyJyb3V0ZXIiOnsicGF0aG5hbWUiOiIvIn19"></div>
47
+ <div id="app"></div>
48
+ <script type="module" src="./app.js"></script>
49
+ </body>
50
+ </html>
@@ -0,0 +1,9 @@
1
+ // Browser stub for @lit-labs/ssr used by halfcab.mjs during client-side demo.
2
+ // The real SSR renderer is only needed on the server. We export a minimal
3
+ // compatible surface so ESM import resolution succeeds in the browser.
4
+
5
+ export function render() {
6
+ throw new Error("@lit-labs/ssr 'render' is a server-only API and should not be called in the browser.");
7
+ }
8
+
9
+ export default { render };
package/halfcab.mjs CHANGED
@@ -2,8 +2,9 @@ import shiftyRouterModule from 'shifty-router'
2
2
  import hrefModule from 'shifty-router/href.js'
3
3
  import historyModule from 'shifty-router/history.js'
4
4
  import createLocation from 'shifty-router/create-location.js'
5
- import bel from 'nanohtml'
6
- import update from 'nanomorph'
5
+ import { html as litHtml, render } from 'lit'
6
+ import { render as renderSSR } from '@lit-labs/ssr'
7
+ import { hydrate } from '@lit-labs/ssr-client'
7
8
  import axios from 'axios'
8
9
  import cssInject from 'csjs-inject'
9
10
  import merge from 'deepmerge'
@@ -11,18 +12,14 @@ import marked from 'marked'
11
12
  import { decode } from 'html-entities'
12
13
  import eventEmitter from './eventEmitter/index.mjs'
13
14
  import qs from 'qs'
14
- import LRU from 'nanolru'
15
- import Component from 'nanocomponent'
16
15
  import * as deepDiff from 'deep-object-diff'
17
16
  import clone from 'fast-clone'
18
17
 
19
- const cache = LRU(5000)
20
-
21
18
  let cssTag = cssInject
22
19
  let componentCSSString = ''
23
20
  let routesArray = []
24
21
  let externalRoutes = []
25
- let state = {}
22
+ let state = {} // TODO - implement diffing using proxy objects rather than deepDiff
26
23
  let router
27
24
  let rootEl
28
25
  let components
@@ -68,20 +65,88 @@ if (typeof window !== 'undefined') {
68
65
 
69
66
  let geb = new eventEmitter({state})
70
67
 
68
+ const stringsCache = new WeakMap()
69
+
71
70
  let html = (strings, ...values) => {
72
- // fix for allowing csjs to coexist with nanohtml SSR
71
+ // fix for allowing csjs to coexist with lit-html
73
72
  values = values.map(value => {
74
- if (value && value.hasOwnProperty('toString')) {
75
- return value.toString()
73
+ if (value && value.hasOwnProperty('toString') && !value.hasOwnProperty('_$litType$')) {
74
+ // Check if it's a template result (lit-html object). If not, and has toString (like CSJS object), stringify it.
75
+ if (Array.isArray(value)) return value;
76
+ if (typeof value === 'object' && value !== null) {
77
+ if (value['_$litType$'] !== undefined) return value; // It's a TemplateResult
78
+ // CSJS object:
79
+ if (value.toString && value.toString !== Object.prototype.toString) {
80
+ return value.toString()
81
+ }
82
+ }
76
83
  }
77
84
  return value
78
85
  })
79
86
 
80
- return bel(strings, ...values)
87
+ // Conversion for onEvent=${fn} to @event=${fn}
88
+ let newStrings = stringsCache.get(strings)
89
+ if (!newStrings) {
90
+ const newRaw = strings.raw ? [...strings.raw] : [...strings]
91
+ const newVals = [...strings]
92
+ const onEventRegex = /on([a-zA-Z]+)=$/
93
+
94
+ for (let i = 0; i < newVals.length; i++) {
95
+ let match = newVals[i].match(onEventRegex)
96
+ if (match) {
97
+ const eventName = match[1]
98
+ const replacement = `@${eventName}=`
99
+ newVals[i] = newVals[i].replace(onEventRegex, replacement)
100
+ newRaw[i] = newRaw[i].replace(onEventRegex, replacement)
101
+ }
102
+ }
103
+
104
+ newStrings = newVals
105
+ newStrings.raw = newRaw
106
+ stringsCache.set(strings, newStrings)
107
+ }
108
+
109
+ return litHtml(newStrings, ...values)
110
+ }
111
+
112
+ // Detect if a container likely contains Lit SSR markers so hydration is safe
113
+ function canHydrateContainer (container) {
114
+ try {
115
+ if (!container || !container.hasChildNodes()) return false
116
+ // Walk comment nodes looking for lit markers inserted by @lit-labs/ssr
117
+ const walker = document.createTreeWalker(
118
+ container,
119
+ NodeFilter.SHOW_COMMENT,
120
+ null,
121
+ false
122
+ )
123
+ let n = walker.nextNode()
124
+ while (n) {
125
+ const data = (n.data || '').toLowerCase()
126
+ if (
127
+ data.includes('lit-part') ||
128
+ data.includes('lit$') ||
129
+ data.includes('lit-ssr')
130
+ ) {
131
+ return true
132
+ }
133
+ n = walker.nextNode()
134
+ }
135
+ } catch (e) {
136
+ // If anything goes wrong, err on the safe side and do not hydrate
137
+ return false
138
+ }
139
+ return false
81
140
  }
82
141
 
83
142
  function ssr (rootComponent) {
84
- let componentsString = `${rootComponent}`
143
+ // Use @lit-labs/ssr render
144
+ // It returns an iterable
145
+ const resultIterator = renderSSR(rootComponent)
146
+ let componentsString = ''
147
+ for (const chunk of resultIterator) {
148
+ componentsString += chunk
149
+ }
85
150
  return {componentsString, stylesString: componentCSSString}
86
151
  }
87
152
 
@@ -243,11 +308,12 @@ function nextTick (func) {
243
308
  function stateUpdated () {
244
309
  if (rootEl) {
245
310
  let startTime = Date.now()
246
- let newEl = components(state)
311
+ let newTemplate = components(state)
247
312
  console.log(`Component render: ${Date.now() - startTime}`)
248
313
  startTime = Date.now()
249
- update(rootEl, newEl)
250
- console.log(`DOM morph: ${Date.now() - startTime}`)
314
+ // Render into the container (rootEl)
315
+ render(newTemplate, rootEl)
316
+ console.log(`DOM update: ${Date.now() - startTime}`)
251
317
  }
252
318
  }
253
319
 
@@ -273,7 +339,8 @@ function updateState (updateObject, options) {
273
339
 
274
340
  debounce(stateUpdated)
275
341
 
276
- if (process.env.NODE_ENV !== 'production') {
342
+ // Avoid referencing process in browsers without a bundler (process is undefined)
343
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production') {
277
344
  console.log('------STATE UPDATE------')
278
345
  console.log(updateObject)
279
346
  console.log(' ')
@@ -286,31 +353,20 @@ function updateState (updateObject, options) {
286
353
  }
287
354
 
288
355
  function emptySSRVideos (c) {
289
- //SSR videos with source tags don't like morphing and you get double audio,
290
- // so remove src from the new one so it never starts
291
- let autoplayTrue = c.querySelectorAll('video[autoplay="true"]')
292
- let autoplayAutoplay = c.querySelectorAll('video[autoplay="autoplay"]')
293
- let autoplayOn = c.querySelectorAll('video[autoplay="on"]')
294
- let selectors = [autoplayTrue, autoplayAutoplay, autoplayOn]
295
- selectors.forEach(selector => {
296
- Array.from(selector).forEach(video => {
297
- video.pause()
298
- Array.from(video.childNodes).forEach(source => {
299
- source.src && (source.src = '')
300
- })
301
- })
302
- })
356
+ // This was for nanomorph. Lit handles updates differently.
357
+ // If we need to manipulate DOM before render, it's harder with Templates.
358
+ // Leaving empty or deprecated.
303
359
  }
304
360
 
305
361
  function injectHTML (htmlString, options) {
306
362
  if (options && options.wrapper === false) {
307
363
  return html([htmlString])
308
364
  }
309
- return html([`<div>${htmlString}</div>`]) // using html as a regular function instead of a tag function, and prevent double encoding of ampersands while we're at it
365
+ return html([`<div>${htmlString}</div>`])
310
366
  }
311
367
 
312
368
  function injectMarkdown (mdString, options) {
313
- return injectHTML(decode(marked(mdString)), options) //using html as a regular function instead of a tag function, and prevent double encoding of ampersands while we're at it
369
+ return injectHTML(decode(marked(mdString)), options)
314
370
  }
315
371
 
316
372
  function gotoRoute (route) {
@@ -422,18 +478,30 @@ export default (config, {shiftyRouter = shiftyRouterModule, href = hrefModule, h
422
478
  gotoRoute(location.href)
423
479
  })
424
480
 
425
- let c = components(state)//root element generated by components
481
+ let c = components(state)// component template
426
482
  if (el) {
427
-
428
- emptySSRVideos(c)
429
-
430
- let r = document.querySelector(el)
431
- rootEl = update(r, c)
483
+ // rootEl is the container
484
+ rootEl = document.querySelector(el)
485
+
486
+ // Initial render. Only hydrate when container has Lit SSR markers.
487
+ if (canHydrateContainer(rootEl)) {
488
+ try {
489
+ hydrate(c, rootEl)
490
+ } catch (e) {
491
+ // Fallback to render if hydration fails (or if not SSR'd by Lit)
492
+ console.warn('Hydration failed or not applicable, falling back to render', e)
493
+ render(c, rootEl)
494
+ }
495
+ } else {
496
+ render(c, rootEl)
497
+ }
498
+
432
499
  return resolve({rootEl, state})
433
500
  }
434
- rootEl = c
435
- resolve({rootEl, state})//if no root element provided, just return the root
436
- // component and the state
501
+ // If no root element provided?
502
+ rootEl = null
503
+ // We return 'c' which is now a TemplateResult.
504
+ resolve({rootEl: c, state})
437
505
  })
438
506
  }
439
507
 
@@ -441,39 +509,6 @@ function rerender () {
441
509
  debounce(stateUpdated)
442
510
  }
443
511
 
444
- class PureComponent extends Component {
445
- createElement (args) {
446
- this.args = clone(args)
447
- }
448
-
449
- update (args) {
450
- let diff = deepDiff.diff(this.args, args)
451
- Object.keys(diff).forEach(key => {
452
- if (typeof diff[key] === 'function') {
453
- this[key] = args[key]
454
- }
455
- })
456
- return !!Object.keys(diff).find(key => typeof diff[key] !== 'function')
457
- }
458
- }
459
-
460
- function cachedComponent (Class, args, id) {
461
- let instance
462
- if (id) {
463
- let found = cache.get(id)
464
- if (found) {
465
- instance = found
466
- } else {
467
- instance = new Class()
468
- cache.set(id, instance)
469
- }
470
- return instance.render(args)
471
- } else {
472
- instance = new Class()
473
- return instance.createElement(args)
474
- }
475
- }
476
-
477
512
  export {
478
513
  getRouteComponent,
479
514
  rerender,
@@ -486,6 +521,7 @@ export {
486
521
  html,
487
522
  defineRoute,
488
523
  updateState,
524
+ state,
489
525
  formField,
490
526
  gotoRoute,
491
527
  cssTag as css,
@@ -494,9 +530,5 @@ export {
494
530
  resetTouched,
495
531
  nextTick,
496
532
  addToHoldingPen,
497
- removeFromHoldingPen,
498
- Component,
499
- LRU,
500
- cachedComponent,
501
- PureComponent
502
- }
533
+ removeFromHoldingPen
534
+ }
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "halfcab",
3
- "version": "15.0.0",
3
+ "version": "15.0.3",
4
4
  "type": "module",
5
5
  "description": "A simple universal JavaScript framework focused on making use of es2015 template strings to build components.",
6
6
  "main": "halfcab.mjs",
7
7
  "module": "halfcab.mjs",
8
8
  "jsnext:main": "halfcab.mjs",
9
9
  "scripts": {
10
- "test": "mocha --experimental-modules --es-module-specifier-resolution=node --experimental-json-modules './{,!(node_modules)/**}/test.js'",
10
+ "test": "mocha './{,!(node_modules)/**}/test.js'",
11
11
  "test:coverage": "c8 --reporter=html --check-coverage --lines 75 --functions 75 --branches 75 npm test",
12
12
  "test:coveralls": "c8 npm test && c8 report --reporter=text-lcov | coveralls",
13
13
  "versionbump:fix": "npm version patch --no-git-tag-version",
14
14
  "versionbump:feature": "npm version major --no-git-tag-version",
15
15
  "versionbump:breakingchanges": "npm version major --no-git-tag-version",
16
- "npm-publish": "npm publish"
16
+ "npm-publish": "npm publish --tag beta",
17
+ "start:example": "npx --yes serve . -l 5173"
17
18
  },
18
19
  "repository": {
19
20
  "type": "git",
@@ -47,6 +48,8 @@
47
48
  "sinon-chai": "^3.0.0"
48
49
  },
49
50
  "dependencies": {
51
+ "@lit-labs/ssr": "^4.0.0",
52
+ "@lit-labs/ssr-client": "^1.1.8",
50
53
  "axios": "^0.26.1",
51
54
  "csjs-inject": "^1.0.1",
52
55
  "deep-object-diff": "^1.1.0",
@@ -54,11 +57,8 @@
54
57
  "event-emitter": "^0.3.5",
55
58
  "fast-clone": "^1.5.13",
56
59
  "html-entities": "^2.3.2",
60
+ "lit": "^3.3.2",
57
61
  "marked": "^0.7.0",
58
- "nanocomponent": "^6.5.2",
59
- "nanohtml": "^1.6.3",
60
- "nanolru": "^1.0.0",
61
- "nanomorph": "^5.4.0",
62
62
  "qs": "^6.5.2",
63
63
  "shifty-router": "^0.1.1"
64
64
  },
package/test.js CHANGED
@@ -3,25 +3,12 @@ import dirtyChai from 'dirty-chai'
3
3
  import sinon from 'sinon'
4
4
  import sinonChai from 'sinon-chai'
5
5
  import jsdomGlobal from 'jsdom-global'
6
- import server from 'nanohtml/lib/server'
7
6
 
8
7
  const {expect} = chai
9
8
  chai.use(dirtyChai)
10
9
  chai.use(sinonChai)
11
10
  chai.use(dirtyChai)
12
11
 
13
- let serverHtml = (strings, ...values) => {
14
- // this duplicates the halfcab html function but uses pelo instead of nanohtml
15
- values = values.map(value => {
16
- if (value && value.hasOwnProperty('toString')) {
17
- return value.toString()
18
- }
19
- return value
20
- })
21
-
22
- return server(strings, ...values)
23
- }
24
-
25
12
  let halfcab, ssr, html, defineRoute, gotoRoute, formField, cache, updateState, injectMarkdown, formIsValid,
26
13
  css, state, getRouteComponent, nextTick
27
14
 
@@ -29,6 +16,11 @@ function intialData (dataInitial) {
29
16
  let el = document.createElement('div')
30
17
  el.setAttribute('data-initial', dataInitial)
31
18
  document.body.appendChild(el)
19
+
20
+ // Also ensure a root element exists for tests that target #root
21
+ let root = document.createElement('div')
22
+ root.id = 'root'
23
+ document.body.appendChild(root)
32
24
  }
33
25
 
34
26
  describe('halfcab', () => {
@@ -36,8 +28,8 @@ describe('halfcab', () => {
36
28
  describe('Server', () => {
37
29
  before(async () => {
38
30
  jsdomGlobal()
39
- intialData('eyJjb250YWN0Rm9ybSI6eyJzZW5kRGlzYWJsZWQiOmZhbHNlLCJzaG93VGhhbmtzIjpmYWxzZX0sImxvZ2luIjp7ImRpc2FibGVkIjpmYWxzZX0sImxvYWRpbmciOmZhbHNlLCJzaG93Q29udGFjdCI6dHJ1ZSwicHJvZHVjdHMiOlt7Im5hbWUiOiJTZWVNb25zdGVyIiwicHJvZHVjdFR5cGUiOiJEaWdpdGFsIFNpZ25hZ2UiLCJkZXNjcmlwdGlvbiI6IkRpZ2l0YWwgc2lnbmFnZSBidWlsdCBmb3Igc2tpIGFyZWFzLiIsInByaWNlIjoiJDMsMjAwIFVTRCBwZXIgd2ludGVyIiwibG9nbyI6eyJ1cmwiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvNmdaY1A2NG1UbTIybTAyWWFvTUdPUS9kN2ZlZjkwZWVhZjI5MmQwNDcwYzNiNjlhODJiMmM2NS9zZWVtb25zdGVyLnN2ZyIsIm5hbWUiOiJTZWVNb25zdGVyIExvZ28ifSwiZGV0YWlsU2VjdGlvbnMiOlt7Im5hbWUiOiJXaGF0IGlzIFNlZU1vbnN0ZXIiLCJkZXNjcmlwdGlvbiI6IiMjIyBTZWVNb25zdGVyIGVuYWJsZXMgeW91IHRvIGRpc3BsYXkgZHluYW1pYyBtZXNzYWdlcyBvbiBzY3JlZW5zIGFyb3VuZCB5b3VyIHNraSBhcmVhIGFuZCBvbiBob3RlbCByb29tIFRWcywgYWxsIG1hbmFnZWQgb3ZlciB0aGUgSW50ZXJuZXQuIiwibGlzdCI6IiIsIm1lZGlhIjp7ImZpbGUiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvYmZtQU5QdE1mUUNRdVE0UWthV1VFLzI0OTY0MjU3OGJmYTBlYzE3M2JlMzhjYTkzMjRhYTEzL3NlZW1vbnN0ZXItMS0yLTMucG5nIiwid2lkdGgiOjg2Nn0sImxpbmsiOiIifSx7Im5hbWUiOiJTZWVNb25zdGVyIEluY2x1ZGVzIiwiZGVzY3JpcHRpb24iOiIiLCJsaXN0IjpbIlBsYXlzIG9uIGFueSBjb21wdXRlciAmIFRWIGNvbWJvIiwiQmFuZHdpZHRoLCBzdG9yYWdlICYgc3VwcG9ydCBpbmNsdWRlZCIsIkVhc3kgZHJhZyBhbmQgZHJvcCBhZG1pbi4iLCJCdWlsdCBpbiBhbmltYXRpb24uIiwiVXBsb2FkIGltYWdlcyBhbmQgdmlkZW8gaW4gYW55IGZvcm1hdCIsIlVzZSB3aXRoIGJvdGggZGlnaXRhbCBzaWducyBhbmQgaW4tcm9vbSBUViBzeXN0ZW1zIiwiVXNlIGxpdmUgZGF0YSB0byBwb3B1bGF0ZSB0ZXh0IGZpZWxkcyAmIHN3YXAgb3V0IGltYWdlcyIsIlJlbW90ZWx5IGNvbnRyb2wgeW91ciBzY3JlZW5zIiwiU2NoZWR1bGUgY29udGVudCIsIlNvY2lhbCBtZWRpYSBmZWVkcyIsIkRpc3BsYXkgdmlkZW9zIGFuZCBwaG90b3MiLCJNYXBzLCBsaWZ0ICYgdHJhaWwgc3RhdHVzIl0sIm1lZGlhIjp7ImZpbGUiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvM3kwQWFQUE16Q0dLaXVLY011ZWEyUS9mNTJiNTc5ZGM5YTIwODBlNDg0OWVlODcyMjYxMjRjMi9RVC13ZWF0aGVyZm9yZWNhc3Qtc2NyZWVuLmpwZyIsIndpZHRoIjo2Njd9LCJsaW5rIjoiIn1dfSx7Im5hbWUiOiJ2aWNvTWFwIiwicHJvZHVjdFR5cGUiOiJJbnRlcmFjdGl2ZSBUcmFpbCBNYXAiLCJkZXNjcmlwdGlvbiI6IipUaGUqIGludGVyYWN0aXZlIG1hcCBmb3Igc2tpIGFyZWFzLiIsInByaWNlIjoiJDIsODAwIFVTRCBwZXIgd2ludGVyIiwibG9nbyI6eyJ1cmwiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvMXNDOU4xV0hXODJBV2tvZUljS1FZMi82ODhjYmI5NmU3YmU2MDJkZDFmOWJmYmFmZjI0ZTI5MC92aWNvbWFwLnN2ZyIsIm5hbWUiOiJ2aWNvTWFwIExvZ28ifSwiZGV0YWlsU2VjdGlvbnMiOlt7Im5hbWUiOiJEZW1vIiwiZGVzY3JpcHRpb24iOiIqVGhlIGJlc3Qgd2F5IHRvIGNoZWNrIG91dCB2aWNvTWFwLCBpcyB0byBqdXN0IHN0YXJ0IHVzaW5nIGl0ISBIYXZlIGEgZ28gd2l0aCB0aGlzIG9uZSBhbmQgbGV0IHVzIGtub3cgd2hhdCB5b3UgdGhpbmsuKiIsImxpc3QiOiIiLCJtZWRpYSI6IiIsImxpbmsiOiJodHRwczovL3ZpY29tYXAtY2RuLnJlc29ydHMtaW50ZXJhY3RpdmUuY29tL21hcC8xMiJ9LHsibmFtZSI6InZpY29NYXAgaW5jbHVkZXMiLCJkZXNjcmlwdGlvbiI6IiIsImxpc3QiOlsiUmVzcG9uc2l2ZSAod29ya3Mgb24gZGVza3RvcCBhbmQgbW9iaWxlIGRldmljZXMpIiwiTGl2ZSBsaWZ0IGFuZCB0cmFpbCBkYXRhIChmcmVlIGJhc2ljIFJlcG9ydCBQYWwgY29ubmVjdG9yIGluY2x1ZGVkKSIsIkNsb3VkIGhvc3RlZCAoQVdTKSIsIkJhbmR3aWR0aCwgc3RvcmFnZSAmIHN1cHBvcnQgaW5jbHVkZWQiLCJFYXN5IHdlYnNpdGUgZW1iZWQiLCJObyBzZXR1cCBjb3N0IC0gYnVpbHQgZnJvbSB5b3VyIGV4aXN0aW5nIElsbHVzdHJhdG9yIGZpbGUiXSwibWVkaWEiOiIiLCJsaW5rIjoiIn1dfSx7Im5hbWUiOiJSZXBvcnQgUGFsIiwicHJvZHVjdFR5cGUiOiJTbm93IFJlcG9ydGluZyIsImRlc2NyaXB0aW9uIjoiRWFzeSBzbm93IHJlcG9ydGluZyBpbiB0aGUgY2xvdWQuIiwicHJpY2UiOiIkMywwMDAgVVNEIHBlciB3aW50ZXIiLCJsb2dvIjp7InVybCI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS82SnBwR2RBV3pLYzhtTThzbUU4dzhtL2FlNWE1ZDQzOTI4MDI3ODY5ZjMyNGFhYTQ2YjZmOTY4L3JlcG9ydHBhbC5zdmciLCJuYW1lIjoiUmVwb3J0IFBhbCBMb2dvIn0sImRldGFpbFNlY3Rpb25zIjpbeyJuYW1lIjoiUmVwb3J0IFBhbCBTdW1tYXJ5IiwiZGVzY3JpcHRpb24iOiJUaGUgaWRlYSBiZWhpbmQgUmVwb3J0IHBhbCBpcyBhIHNpbXBsZSBvbmUgLSBtb3ZlIHNub3cgcmVwb3J0aW5nIGF3YXkgZnJvbSB5b3VyIHdlYnNpdGUgQ01TIGFuZCBpbnRvIHRoZSBjbG91ZC5cblxuSW5zdGVhZCBvZiBjb250cm9sbGluZyB5b3VyIHNub3cgcmVwb3J0IGZyb20geW91ciB3ZWJzaXRlICh3aGljaCBjYW4gY2hhbmdlIGV2ZXJ5IGNvdXBsZSBvZiB5ZWFycywgcmVzdWx0aW5nIGluIG1vcmUgY29zdHMgdG8gYnVpbGQsIHRpbWUgcmUtdHJhaW5pbmcgc3RhZmYgYW5kIGhhdmluZyB0byByZS1pbnRlZ3JhdGUgd2l0aCAzcmQgcGFydGllcyksIG1vdmUgaXQgaW50byB0aGUgY2xvdWQgYW5kIGhhdmUgYSBjb25zaXN0ZW50IHBsYXRmb3JtLCBzZWFzb24gYWZ0ZXIgc2Vhc29uLlxuXG5SZXBvcnQgUGFsIG1ha2VzIHVzZSBvZiB0aGUgbGF0ZXN0IHRlY2hub2xvZ2llcyBvbiBvZmZlciBmcm9tIEFtYXpvbiBXZWIgU2VydmljZXMsIGdpdmluZyB5b3UgYSByb2NrIHNvbGlkLCBhbHdheXMgb24gc25vdyByZXBvcnRpbmcgcGxhdGZvcm0gdGhhdCdzIGxpZ2h0bmluZyBmYXN0LlxuXG5FdmVyeSBza2kgcmVzb3J0IGlzIGRpZmZlcmVudCwgc28gUmVwb3J0IFBhbCBoYXMgYmVlbiBidWlsdCBmcm9tIHRoZSBncm91bmQgdXAgc28gdGhhdCB3ZSBjYW4gY3VzdG9taXNlIGl0IHRvIHN1aXQgeW91LiBObyBtb3JlIHdvcmtpbmcgeW91ciB3YXkgYXJvdW5kIGxpbWl0YXRpb25zIG9mIHlvdXIgd2Vic2l0ZSBDTVMsIHlvdSBjYW4gaW5wdXQgYW5kIG91dHB1dCB3aGF0IHlvdSBsaWtlLlxuXG5XZSdsbCBzZXR1cCB5b3VyIG91dHB1dCBpbiBbTVROLlhNTF0oaHR0cDovL210bnhtbC5vcmcgXCJNVE4uWE1MXCIpIGZvcm1hdCAtIHRoZSBpbmR1c3RyeSBzdGFuZGFyZC4gWW91ciBkYXRhIHdpbGwgYmUgZWFzaWx5IHNoYXJlZCB3aXRoIDNyZCBwYXJ0aWVzLiIsImxpc3QiOiIiLCJtZWRpYSI6eyJmaWxlIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxLzFaUHlDVFhjMDh3NlU4Z0MyazZTZ20vNmI1YjJhNzczOTVlYzE5NjVlMDI0NmJmOGJlOGU3NGEvU2NyZWVuLVNob3QtMjAxNi0xMi0xMS1hdC00LjE4LjM0LXBtLnBuZyIsIndpZHRoIjozODV9LCJsaW5rIjoiIn0seyJuYW1lIjoiUmVwb3J0IFBhbCBJbmNsdWRlcyIsImRlc2NyaXB0aW9uIjoiIiwibGlzdCI6WyJVbmxpbWl0ZWQgdXNlcnMiLCJJbnRlbGxpZ2VudCByZXBvcnQgbWVyZ2luZyIsIlNjaGVkdWxlZCByZXBvcnRzIiwiU29jaWFsIG1lZGlhIGludGVncmF0aW9uIiwiTVROLlhNTCBvdXRwdXQiLCJSZXBvcnQgaGlzdG9yeSBhbmQgcm9sbGJhY2siLCJBdXRvbWF0ZWQgbGlmdCAmIHRyYWlsIG9wZW4gY291bnRzLCBhY3JlYWdlIGFuZCBsZW5ndGgiLCJSb2NrIHNvbGlkIC0gY2xvdWQgaG9zdGVkIG9uIEFtYXpvbiBXZWIgU2VydmljZXMiXSwibWVkaWEiOiIiLCJsaW5rIjoiIn1dfV0sImNvbXBhbnkiOnsibmFtZSI6IlJlc29ydHMgSW50ZXJhY3RpdmUiLCJkZXNjcmlwdGlvbiI6IlJlc29ydHMgSW50ZXJhY3RpdmUgaGFzIGJlZW4gbWFraW5nIGNsb3VkIHNvZnR3YXJlIGZvciB0aGUgc2tpIGluZHVzdHJ5IGZvciBzaW5jZSAyMDA0LiBUaGUgd2F5IG91ciBzb2Z0d2FyZSBpcyBidWlsdCBhbGxvd3MgdXMgdG8gZXZvbHZlIG92ZXIgdGltZSBhbmQgZW1icmFjZSBjaGFuZ2luZyB0ZWNobm9sb2dpZXMg4oCTIGEgbXVzdCBoYXZlIHF1YWxpdHkgb2YgYW55IGNsb3VkIGJhc2VkIHBhcnRuZXIuIE91ciBjdXJyZW50IHN1aXRlIG9mIFJlcG9ydCBQYWwsIHZpY29NYXAgYW5kIFNlZU1vbnN0ZXIgY292ZXJzIGEgYnJvYWQgcmFuZ2Ugb2Ygc2tpIGFyZWEgbWFya2V0aW5nIGFuZCBvcGVyYXRpb25zIGFuZCBhbGxvd3MgeW91IHRvIGdldCBiYWNrIHRvIHdoYXTigJlzIGltcG9ydGFudCAobGlrZSB0YWtpbmcgYSBmZXcgcnVucyBvbiB5b3VyIGx1bmNoIGJyZWFrISkgR2V0IGluIHRvdWNoIHRvZGF5LCB3ZeKAmWQgbG92ZSB0byB0YWxrLiIsImxvZ28iOnsidXJsIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxLzZydjJ0QnBZNXlpRVVveUlDdTBBNFcvNGFiOTE1MTM0MzY4Nzg5YzEzNDNjMzQ0NDAwNWUzYWEvcmVzb3J0c2ludGVyYWN0aXZlLnN2ZyIsIm5hbWUiOiJSZXNvcnRzIEludGVyYWN0aXZlIExvZ28ifSwiY29tcGFueURldGFpbFNlY3Rpb25zIjpbeyJpbWFnZSI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS82SW1WemxCWFNFaUtTZWVxZ0VRa3NnL2M3NDhhZjU3NDkxZjc4MDQ4NDUwMzhiNGM2YTE3N2E4L3Bvd2Rlcl9zaG90LmpwZyIsInRpdGxlIjoiUmVzb3J0cyBJbnRlcmFjdGl2ZSB3ZWJzaXRlIiwiYm9keSI6IiJ9LHsiaW1hZ2UiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvM3kwQWFQUE16Q0dLaXVLY011ZWEyUS9mNTJiNTc5ZGM5YTIwODBlNDg0OWVlODcyMjYxMjRjMi9RVC13ZWF0aGVyZm9yZWNhc3Qtc2NyZWVuLmpwZyIsInRpdGxlIjoiV2hhdCBzZXRzIHVzIGFwYXJ0PyIsImJvZHkiOiJSZXNvcnRzIEludGVyYWN0aXZlIGhhcyBiZWVuIG1ha2luZyBjbG91ZCBzb2Z0d2FyZSBmb3IgdGhlIHNraSBpbmR1c3RyeSBzaW5jZSAyMDA1IHNvIHdlIGtub3cgYSB0aGluZyBvciB0d28gYWJvdXQgdGhlIHN1YmplY3QhIFRoZSB3YXkgb3VyIHNvZnR3YXJlIGlzIGJ1aWx0IGFsbG93cyB1cyB0byBldm9sdmUgb3ZlciB0aW1lIGFuZCBlbWJyYWNlIGNoYW5naW5nIHRlY2hub2xvZ2llcyDigJMgYSBtdXN0IGhhdmUgcXVhbGl0eSBvZiBhbnkgY2xvdWQgYmFzZWQgcGFydG5lci4gT3VyIGN1cnJlbnQgc3VpdGUgb2YgUmVwb3J0IFBhbCwgdmljb01hcCBhbmQgU2VlTW9uc3RlciBjb3ZlcnMgYSBicm9hZCByYW5nZSBvZiBza2kgYXJlYSBtYXJrZXRpbmcgYW5kIG9wZXJhdGlvbnMgYW5kIGFsbG93cyB5b3UgdG8gZ2V0IGJhY2sgdG8gd2hhdOKAmXMgaW1wb3J0YW50IChsaWtlIHRha2luZyBhIGZldyBydW5zIG9uIHlvdXIgbHVuY2ggYnJlYWshKSBbR2V0IGluIHRvdWNoIHRvZGF5LCB3ZeKAmWQgbG92ZSB0byB0YWxrLl0obWFpbHRvOmluZm9AcmVzb3J0cy1pbnRlcmFjdGl2ZS5jb20pIn0seyJpbWFnZSI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS80ZVJ3ZDkyZ293b21NS0FZT2dnY3cwLzlmODNjMmYyYjJhN2VhZWIxMzJhOWRmZTYwNTA5YmNhL0RldmljZXMuanBnIiwidGl0bGUiOiJDbGV2ZXIgU29mdHdhcmUgTWFraW5nIExpZmUgRWFzeSIsImJvZHkiOiItIFlvdSB3b3JrIGluIHRoZSBza2kgaW5kdXN0cnkgYmVjYXVzZSB5b3UgbG92ZSBpdCwgc28gd2UgYnVpbGQgc29mdHdhcmUgdG8gc2F2ZSB5b3UgdGltZSBhbmQgZ2V0IHlvdSBiYWNrIG9uIHRoZSBoaWxsXG4tIENsb3VkIGJhc2VkIHNvZnR3YXJlIGlzIGRlc2lnbmVkIHRvIGJlIHVzZWQgYW55d2hlcmUg4oCTIGRlc2t0b3AsIG1vYmlsZSwgQ29sb3JhZG8sIFRpbWJ1a3R1LiBJZiB5b3UgY2FuIGdldCBhbiBJbnRlcm5ldCBjb25uZWN0aW9uLCB3ZeKAmXZlIGdvdCB5b3UgY292ZXJlZFxuLSBTaW1wbGljaXR5IGFuZCBzdGFiaWxpdHkgZm9ybSBhIGNvcmUgcGFydCBvZiBvdXIgc29mdHdhcmUgYXJjaGl0ZWN0dXJlLiBPdXIgcHJvZHVjdHMgYXJlIGhvc3RlZCBvbiBBbWF6b24gV2ViIFNlcnZpY2VzIGFuZCBtYWtlIHVzZSBvZiB0aGVpciBiZXN0IGFuZCBsYXRlc3QgdGVjaG5vbG9naWVzIGxpa2UgQ2xvdWRGcm9udCwgQXVyb3JhLCBhbmQgRWxhc3RpYyBCZWFuc3RhbGsgdG8gbWFrZSBzdXJlIHdl4oCZcmUgYWx3YXlzIGZhc3QgYW5kIGF2YWlsYWJsZVxuLSBJbmNsdWRlIGxpdmUgZGF0YSBhbmQgaW1hZ2VzIGZyb20gc29jaWFsIG1lZGlhLCB3ZWF0aGVyLCBuZXdzIGFuZCBvdGhlciB3ZWJzaXRlc1xuLSBVcGxvYWQgdmlkZW8gaW4gYW55IGZvcm1hdCBhbmQgaXTigJlsbCBiZSBjb252ZXJ0ZWQgZm9yIHlvdSJ9XSwiaWNvbiI6eyJzeXMiOnsic3BhY2UiOnsic3lzIjp7InR5cGUiOiJMaW5rIiwibGlua1R5cGUiOiJTcGFjZSIsImlkIjoiNnA4b2h4ZmlrYWsxIn19LCJpZCI6ImttRzBYM2ZGUWNFc2VpbWNlVWtJcyIsInR5cGUiOiJBc3NldCIsImNyZWF0ZWRBdCI6IjIwMTctMDYtMThUMTA6MjQ6NTQuNTgwWiIsInVwZGF0ZWRBdCI6IjIwMTctMDYtMThUMTA6MjQ6NTQuNTgwWiIsInJldmlzaW9uIjoxLCJsb2NhbGUiOiJlbi1OWiJ9LCJmaWVsZHMiOnsidGl0bGUiOiJTZW1pLWZsYWtlLXBuZyIsImRlc2NyaXB0aW9uIjoiUE5HIHZlcnNpb24gb2Ygc2VtaS1mbGFrZSIsImZpbGUiOnsidXJsIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxL2ttRzBYM2ZGUWNFc2VpbWNlVWtJcy85NDNjMDQ3OTFhMmQ0NDJmMWY1MDgwMmEzODY4ZTI5ZS9zZW1pZmxha2UucG5nIiwiZGV0YWlscyI6eyJzaXplIjoyNDE5MCwiaW1hZ2UiOnsid2lkdGgiOjUxMiwiaGVpZ2h0Ijo1MTJ9fSwiZmlsZU5hbWUiOiJzZW1pZmxha2UucG5nIiwiY29udGVudFR5cGUiOiJpbWFnZS9wbmcifX19fX0=')
40
- let halfcabModule = await import('./halfcab')
31
+ intialData('eyJjb250YWN0Ijp7InRpdGxlIjoiQ29udGFjdCBVcyJ9LCJyb3V0ZXIiOnsicGF0aG5hbWUiOiIvIn19')
32
+ let halfcabModule = await import('./halfcab.mjs')
41
33
  ;({
42
34
  ssr,
43
35
  html,
@@ -60,8 +52,10 @@ describe('halfcab', () => {
60
52
  width: 100px;
61
53
  }
62
54
  `
63
- let {componentsString, stylesString} = ssr(serverHtml`
64
- <div class="${style.myStyle}" oninput=${() => {
55
+ // Use html instead of serverHtml (which depended on nanohtml)
56
+ // lit-html on server via ssr() should return string
57
+ let {componentsString, stylesString} = ssr(html`
58
+ <div class="${style.myStyle}" @input=${() => {
65
59
  }}></div>
66
60
  `)
67
61
  expect(typeof componentsString === 'string').to.be.true()
@@ -72,8 +66,8 @@ describe('halfcab', () => {
72
66
 
73
67
  before(async () => {
74
68
  jsdomGlobal()
75
- intialData('eyJjb250YWN0Rm9ybSI6eyJzZW5kRGlzYWJsZWQiOmZhbHNlLCJzaG93VGhhbmtzIjpmYWxzZX0sImxvZ2luIjp7ImRpc2FibGVkIjpmYWxzZX0sImxvYWRpbmciOmZhbHNlLCJzaG93Q29udGFjdCI6dHJ1ZSwicHJvZHVjdHMiOlt7Im5hbWUiOiJTZWVNb25zdGVyIiwicHJvZHVjdFR5cGUiOiJEaWdpdGFsIFNpZ25hZ2UiLCJkZXNjcmlwdGlvbiI6IkRpZ2l0YWwgc2lnbmFnZSBidWlsdCBmb3Igc2tpIGFyZWFzLiIsInByaWNlIjoiJDMsMjAwIFVTRCBwZXIgd2ludGVyIiwibG9nbyI6eyJ1cmwiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvNmdaY1A2NG1UbTIybTAyWWFvTUdPUS9kN2ZlZjkwZWVhZjI5MmQwNDcwYzNiNjlhODJiMmM2NS9zZWVtb25zdGVyLnN2ZyIsIm5hbWUiOiJTZWVNb25zdGVyIExvZ28ifSwiZGV0YWlsU2VjdGlvbnMiOlt7Im5hbWUiOiJXaGF0IGlzIFNlZU1vbnN0ZXIiLCJkZXNjcmlwdGlvbiI6IiMjIyBTZWVNb25zdGVyIGVuYWJsZXMgeW91IHRvIGRpc3BsYXkgZHluYW1pYyBtZXNzYWdlcyBvbiBzY3JlZW5zIGFyb3VuZCB5b3VyIHNraSBhcmVhIGFuZCBvbiBob3RlbCByb29tIFRWcywgYWxsIG1hbmFnZWQgb3ZlciB0aGUgSW50ZXJuZXQuIiwibGlzdCI6IiIsIm1lZGlhIjp7ImZpbGUiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvYmZtQU5QdE1mUUNRdVE0UWthV1VFLzI0OTY0MjU3OGJmYTBlYzE3M2JlMzhjYTkzMjRhYTEzL3NlZW1vbnN0ZXItMS0yLTMucG5nIiwid2lkdGgiOjg2Nn0sImxpbmsiOiIifSx7Im5hbWUiOiJTZWVNb25zdGVyIEluY2x1ZGVzIiwiZGVzY3JpcHRpb24iOiIiLCJsaXN0IjpbIlBsYXlzIG9uIGFueSBjb21wdXRlciAmIFRWIGNvbWJvIiwiQmFuZHdpZHRoLCBzdG9yYWdlICYgc3VwcG9ydCBpbmNsdWRlZCIsIkVhc3kgZHJhZyBhbmQgZHJvcCBhZG1pbi4iLCJCdWlsdCBpbiBhbmltYXRpb24uIiwiVXBsb2FkIGltYWdlcyBhbmQgdmlkZW8gaW4gYW55IGZvcm1hdCIsIlVzZSB3aXRoIGJvdGggZGlnaXRhbCBzaWducyBhbmQgaW4tcm9vbSBUViBzeXN0ZW1zIiwiVXNlIGxpdmUgZGF0YSB0byBwb3B1bGF0ZSB0ZXh0IGZpZWxkcyAmIHN3YXAgb3V0IGltYWdlcyIsIlJlbW90ZWx5IGNvbnRyb2wgeW91ciBzY3JlZW5zIiwiU2NoZWR1bGUgY29udGVudCIsIlNvY2lhbCBtZWRpYSBmZWVkcyIsIkRpc3BsYXkgdmlkZW9zIGFuZCBwaG90b3MiLCJNYXBzLCBsaWZ0ICYgdHJhaWwgc3RhdHVzIl0sIm1lZGlhIjp7ImZpbGUiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvM3kwQWFQUE16Q0dLaXVLY011ZWEyUS9mNTJiNTc5ZGM5YTIwODBlNDg0OWVlODcyMjYxMjRjMi9RVC13ZWF0aGVyZm9yZWNhc3Qtc2NyZWVuLmpwZyIsIndpZHRoIjo2Njd9LCJsaW5rIjoiIn1dfSx7Im5hbWUiOiJ2aWNvTWFwIiwicHJvZHVjdFR5cGUiOiJJbnRlcmFjdGl2ZSBUcmFpbCBNYXAiLCJkZXNjcmlwdGlvbiI6IipUaGUqIGludGVyYWN0aXZlIG1hcCBmb3Igc2tpIGFyZWFzLiIsInByaWNlIjoiJDIsODAwIFVTRCBwZXIgd2ludGVyIiwibG9nbyI6eyJ1cmwiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvMXNDOU4xV0hXODJBV2tvZUljS1FZMi82ODhjYmI5NmU3YmU2MDJkZDFmOWJmYmFmZjI0ZTI5MC92aWNvbWFwLnN2ZyIsIm5hbWUiOiJ2aWNvTWFwIExvZ28ifSwiZGV0YWlsU2VjdGlvbnMiOlt7Im5hbWUiOiJEZW1vIiwiZGVzY3JpcHRpb24iOiIqVGhlIGJlc3Qgd2F5IHRvIGNoZWNrIG91dCB2aWNvTWFwLCBpcyB0byBqdXN0IHN0YXJ0IHVzaW5nIGl0ISBIYXZlIGEgZ28gd2l0aCB0aGlzIG9uZSBhbmQgbGV0IHVzIGtub3cgd2hhdCB5b3UgdGhpbmsuKiIsImxpc3QiOiIiLCJtZWRpYSI6IiIsImxpbmsiOiJodHRwczovL3ZpY29tYXAtY2RuLnJlc29ydHMtaW50ZXJhY3RpdmUuY29tL21hcC8xMiJ9LHsibmFtZSI6InZpY29NYXAgaW5jbHVkZXMiLCJkZXNjcmlwdGlvbiI6IiIsImxpc3QiOlsiUmVzcG9uc2l2ZSAod29ya3Mgb24gZGVza3RvcCBhbmQgbW9iaWxlIGRldmljZXMpIiwiTGl2ZSBsaWZ0IGFuZCB0cmFpbCBkYXRhIChmcmVlIGJhc2ljIFJlcG9ydCBQYWwgY29ubmVjdG9yIGluY2x1ZGVkKSIsIkNsb3VkIGhvc3RlZCAoQVdTKSIsIkJhbmR3aWR0aCwgc3RvcmFnZSAmIHN1cHBvcnQgaW5jbHVkZWQiLCJFYXN5IHdlYnNpdGUgZW1iZWQiLCJObyBzZXR1cCBjb3N0IC0gYnVpbHQgZnJvbSB5b3VyIGV4aXN0aW5nIElsbHVzdHJhdG9yIGZpbGUiXSwibWVkaWEiOiIiLCJsaW5rIjoiIn1dfSx7Im5hbWUiOiJSZXBvcnQgUGFsIiwicHJvZHVjdFR5cGUiOiJTbm93IFJlcG9ydGluZyIsImRlc2NyaXB0aW9uIjoiRWFzeSBzbm93IHJlcG9ydGluZyBpbiB0aGUgY2xvdWQuIiwicHJpY2UiOiIkMywwMDAgVVNEIHBlciB3aW50ZXIiLCJsb2dvIjp7InVybCI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS82SnBwR2RBV3pLYzhtTThzbUU4dzhtL2FlNWE1ZDQzOTI4MDI3ODY5ZjMyNGFhYTQ2YjZmOTY4L3JlcG9ydHBhbC5zdmciLCJuYW1lIjoiUmVwb3J0IFBhbCBMb2dvIn0sImRldGFpbFNlY3Rpb25zIjpbeyJuYW1lIjoiUmVwb3J0IFBhbCBTdW1tYXJ5IiwiZGVzY3JpcHRpb24iOiJUaGUgaWRlYSBiZWhpbmQgUmVwb3J0IHBhbCBpcyBhIHNpbXBsZSBvbmUgLSBtb3ZlIHNub3cgcmVwb3J0aW5nIGF3YXkgZnJvbSB5b3VyIHdlYnNpdGUgQ01TIGFuZCBpbnRvIHRoZSBjbG91ZC5cblxuSW5zdGVhZCBvZiBjb250cm9sbGluZyB5b3VyIHNub3cgcmVwb3J0IGZyb20geW91ciB3ZWJzaXRlICh3aGljaCBjYW4gY2hhbmdlIGV2ZXJ5IGNvdXBsZSBvZiB5ZWFycywgcmVzdWx0aW5nIGluIG1vcmUgY29zdHMgdG8gYnVpbGQsIHRpbWUgcmUtdHJhaW5pbmcgc3RhZmYgYW5kIGhhdmluZyB0byByZS1pbnRlZ3JhdGUgd2l0aCAzcmQgcGFydGllcyksIG1vdmUgaXQgaW50byB0aGUgY2xvdWQgYW5kIGhhdmUgYSBjb25zaXN0ZW50IHBsYXRmb3JtLCBzZWFzb24gYWZ0ZXIgc2Vhc29uLlxuXG5SZXBvcnQgUGFsIG1ha2VzIHVzZSBvZiB0aGUgbGF0ZXN0IHRlY2hub2xvZ2llcyBvbiBvZmZlciBmcm9tIEFtYXpvbiBXZWIgU2VydmljZXMsIGdpdmluZyB5b3UgYSByb2NrIHNvbGlkLCBhbHdheXMgb24gc25vdyByZXBvcnRpbmcgcGxhdGZvcm0gdGhhdCdzIGxpZ2h0bmluZyBmYXN0LlxuXG5FdmVyeSBza2kgcmVzb3J0IGlzIGRpZmZlcmVudCwgc28gUmVwb3J0IFBhbCBoYXMgYmVlbiBidWlsdCBmcm9tIHRoZSBncm91bmQgdXAgc28gdGhhdCB3ZSBjYW4gY3VzdG9taXNlIGl0IHRvIHN1aXQgeW91LiBObyBtb3JlIHdvcmtpbmcgeW91ciB3YXkgYXJvdW5kIGxpbWl0YXRpb25zIG9mIHlvdXIgd2Vic2l0ZSBDTVMsIHlvdSBjYW4gaW5wdXQgYW5kIG91dHB1dCB3aGF0IHlvdSBsaWtlLlxuXG5XZSdsbCBzZXR1cCB5b3VyIG91dHB1dCBpbiBbTVROLlhNTF0oaHR0cDovL210bnhtbC5vcmcgXCJNVE4uWE1MXCIpIGZvcm1hdCAtIHRoZSBpbmR1c3RyeSBzdGFuZGFyZC4gWW91ciBkYXRhIHdpbGwgYmUgZWFzaWx5IHNoYXJlZCB3aXRoIDNyZCBwYXJ0aWVzLiIsImxpc3QiOiIiLCJtZWRpYSI6eyJmaWxlIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxLzFaUHlDVFhjMDh3NlU4Z0MyazZTZ20vNmI1YjJhNzczOTVlYzE5NjVlMDI0NmJmOGJlOGU3NGEvU2NyZWVuLVNob3QtMjAxNi0xMi0xMS1hdC00LjE4LjM0LXBtLnBuZyIsIndpZHRoIjozODV9LCJsaW5rIjoiIn0seyJuYW1lIjoiUmVwb3J0IFBhbCBJbmNsdWRlcyIsImRlc2NyaXB0aW9uIjoiIiwibGlzdCI6WyJVbmxpbWl0ZWQgdXNlcnMiLCJJbnRlbGxpZ2VudCByZXBvcnQgbWVyZ2luZyIsIlNjaGVkdWxlZCByZXBvcnRzIiwiU29jaWFsIG1lZGlhIGludGVncmF0aW9uIiwiTVROLlhNTCBvdXRwdXQiLCJSZXBvcnQgaGlzdG9yeSBhbmQgcm9sbGJhY2siLCJBdXRvbWF0ZWQgbGlmdCAmIHRyYWlsIG9wZW4gY291bnRzLCBhY3JlYWdlIGFuZCBsZW5ndGgiLCJSb2NrIHNvbGlkIC0gY2xvdWQgaG9zdGVkIG9uIEFtYXpvbiBXZWIgU2VydmljZXMiXSwibWVkaWEiOiIiLCJsaW5rIjoiIn1dfV0sImNvbXBhbnkiOnsibmFtZSI6IlJlc29ydHMgSW50ZXJhY3RpdmUiLCJkZXNjcmlwdGlvbiI6IlJlc29ydHMgSW50ZXJhY3RpdmUgaGFzIGJlZW4gbWFraW5nIGNsb3VkIHNvZnR3YXJlIGZvciB0aGUgc2tpIGluZHVzdHJ5IGZvciBzaW5jZSAyMDA0LiBUaGUgd2F5IG91ciBzb2Z0d2FyZSBpcyBidWlsdCBhbGxvd3MgdXMgdG8gZXZvbHZlIG92ZXIgdGltZSBhbmQgZW1icmFjZSBjaGFuZ2luZyB0ZWNobm9sb2dpZXMg4oCTIGEgbXVzdCBoYXZlIHF1YWxpdHkgb2YgYW55IGNsb3VkIGJhc2VkIHBhcnRuZXIuIE91ciBjdXJyZW50IHN1aXRlIG9mIFJlcG9ydCBQYWwsIHZpY29NYXAgYW5kIFNlZU1vbnN0ZXIgY292ZXJzIGEgYnJvYWQgcmFuZ2Ugb2Ygc2tpIGFyZWEgbWFya2V0aW5nIGFuZCBvcGVyYXRpb25zIGFuZCBhbGxvd3MgeW91IHRvIGdldCBiYWNrIHRvIHdoYXTigJlzIGltcG9ydGFudCAobGlrZSB0YWtpbmcgYSBmZXcgcnVucyBvbiB5b3VyIGx1bmNoIGJyZWFrISkgR2V0IGluIHRvdWNoIHRvZGF5LCB3ZeKAmWQgbG92ZSB0byB0YWxrLiIsImxvZ28iOnsidXJsIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxLzZydjJ0QnBZNXlpRVVveUlDdTBBNFcvNGFiOTE1MTM0MzY4Nzg5YzEzNDNjMzQ0NDAwNWUzYWEvcmVzb3J0c2ludGVyYWN0aXZlLnN2ZyIsIm5hbWUiOiJSZXNvcnRzIEludGVyYWN0aXZlIExvZ28ifSwiY29tcGFueURldGFpbFNlY3Rpb25zIjpbeyJpbWFnZSI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS82SW1WemxCWFNFaUtTZWVxZ0VRa3NnL2M3NDhhZjU3NDkxZjc4MDQ4NDUwMzhiNGM2YTE3N2E4L3Bvd2Rlcl9zaG90LmpwZyIsInRpdGxlIjoiUmVzb3J0cyBJbnRlcmFjdGl2ZSB3ZWJzaXRlIiwiYm9keSI6IiJ9LHsiaW1hZ2UiOiIvL2ltYWdlcy5jb250ZW50ZnVsLmNvbS82cDhvaHhmaWthazEvM3kwQWFQUE16Q0dLaXVLY011ZWEyUS9mNTJiNTc5ZGM5YTIwODBlNDg0OWVlODcyMjYxMjRjMi9RVC13ZWF0aGVyZm9yZWNhc3Qtc2NyZWVuLmpwZyIsInRpdGxlIjoiV2hhdCBzZXRzIHVzIGFwYXJ0PyIsImJvZHkiOiJSZXNvcnRzIEludGVyYWN0aXZlIGhhcyBiZWVuIG1ha2luZyBjbG91ZCBzb2Z0d2FyZSBmb3IgdGhlIHNraSBpbmR1c3RyeSBzaW5jZSAyMDA1IHNvIHdlIGtub3cgYSB0aGluZyBvciB0d28gYWJvdXQgdGhlIHN1YmplY3QhIFRoZSB3YXkgb3VyIHNvZnR3YXJlIGlzIGJ1aWx0IGFsbG93cyB1cyB0byBldm9sdmUgb3ZlciB0aW1lIGFuZCBlbWJyYWNlIGNoYW5naW5nIHRlY2hub2xvZ2llcyDigJMgYSBtdXN0IGhhdmUgcXVhbGl0eSBvZiBhbnkgY2xvdWQgYmFzZWQgcGFydG5lci4gT3VyIGN1cnJlbnQgc3VpdGUgb2YgUmVwb3J0IFBhbCwgdmljb01hcCBhbmQgU2VlTW9uc3RlciBjb3ZlcnMgYSBicm9hZCByYW5nZSBvZiBza2kgYXJlYSBtYXJrZXRpbmcgYW5kIG9wZXJhdGlvbnMgYW5kIGFsbG93cyB5b3UgdG8gZ2V0IGJhY2sgdG8gd2hhdOKAmXMgaW1wb3J0YW50IChsaWtlIHRha2luZyBhIGZldyBydW5zIG9uIHlvdXIgbHVuY2ggYnJlYWshKSBbR2V0IGluIHRvdWNoIHRvZGF5LCB3ZeKAmWQgbG92ZSB0byB0YWxrLl0obWFpbHRvOmluZm9AcmVzb3J0cy1pbnRlcmFjdGl2ZS5jb20pIn0seyJpbWFnZSI6Ii8vaW1hZ2VzLmNvbnRlbnRmdWwuY29tLzZwOG9oeGZpa2FrMS80ZVJ3ZDkyZ293b21NS0FZT2dnY3cwLzlmODNjMmYyYjJhN2VhZWIxMzJhOWRmZTYwNTA5YmNhL0RldmljZXMuanBnIiwidGl0bGUiOiJDbGV2ZXIgU29mdHdhcmUgTWFraW5nIExpZmUgRWFzeSIsImJvZHkiOiItIFlvdSB3b3JrIGluIHRoZSBza2kgaW5kdXN0cnkgYmVjYXVzZSB5b3UgbG92ZSBpdCwgc28gd2UgYnVpbGQgc29mdHdhcmUgdG8gc2F2ZSB5b3UgdGltZSBhbmQgZ2V0IHlvdSBiYWNrIG9uIHRoZSBoaWxsXG4tIENsb3VkIGJhc2VkIHNvZnR3YXJlIGlzIGRlc2lnbmVkIHRvIGJlIHVzZWQgYW55d2hlcmUg4oCTIGRlc2t0b3AsIG1vYmlsZSwgQ29sb3JhZG8sIFRpbWJ1a3R1LiBJZiB5b3UgY2FuIGdldCBhbiBJbnRlcm5ldCBjb25uZWN0aW9uLCB3ZeKAmXZlIGdvdCB5b3UgY292ZXJlZFxuLSBTaW1wbGljaXR5IGFuZCBzdGFiaWxpdHkgZm9ybSBhIGNvcmUgcGFydCBvZiBvdXIgc29mdHdhcmUgYXJjaGl0ZWN0dXJlLiBPdXIgcHJvZHVjdHMgYXJlIGhvc3RlZCBvbiBBbWF6b24gV2ViIFNlcnZpY2VzIGFuZCBtYWtlIHVzZSBvZiB0aGVpciBiZXN0IGFuZCBsYXRlc3QgdGVjaG5vbG9naWVzIGxpa2UgQ2xvdWRGcm9udCwgQXVyb3JhLCBhbmQgRWxhc3RpYyBCZWFuc3RhbGsgdG8gbWFrZSBzdXJlIHdl4oCZcmUgYWx3YXlzIGZhc3QgYW5kIGF2YWlsYWJsZVxuLSBJbmNsdWRlIGxpdmUgZGF0YSBhbmQgaW1hZ2VzIGZyb20gc29jaWFsIG1lZGlhLCB3ZWF0aGVyLCBuZXdzIGFuZCBvdGhlciB3ZWJzaXRlc1xuLSBVcGxvYWQgdmlkZW8gaW4gYW55IGZvcm1hdCBhbmQgaXTigJlsbCBiZSBjb252ZXJ0ZWQgZm9yIHlvdSJ9XSwiaWNvbiI6eyJzeXMiOnsic3BhY2UiOnsic3lzIjp7InR5cGUiOiJMaW5rIiwibGlua1R5cGUiOiJTcGFjZSIsImlkIjoiNnA4b2h4ZmlrYWsxIn19LCJpZCI6ImttRzBYM2ZGUWNFc2VpbWNlVWtJcyIsInR5cGUiOiJBc3NldCIsImNyZWF0ZWRBdCI6IjIwMTctMDYtMThUMTA6MjQ6NTQuNTgwWiIsInVwZGF0ZWRBdCI6IjIwMTctMDYtMThUMTA6MjQ6NTQuNTgwWiIsInJldmlzaW9uIjoxLCJsb2NhbGUiOiJlbi1OWiJ9LCJmaWVsZHMiOnsidGl0bGUiOiJTZW1pLWZsYWtlLXBuZyIsImRlc2NyaXB0aW9uIjoiUE5HIHZlcnNpb24gb2Ygc2VtaS1mbGFrZSIsImZpbGUiOnsidXJsIjoiLy9pbWFnZXMuY29udGVudGZ1bC5jb20vNnA4b2h4ZmlrYWsxL2ttRzBYM2ZGUWNFc2VpbWNlVWtJcy85NDNjMDQ3OTFhMmQ0NDJmMWY1MDgwMmEzODY4ZTI5ZS9zZW1pZmxha2UucG5nIiwiZGV0YWlscyI6eyJzaXplIjoyNDE5MCwiaW1hZ2UiOnsid2lkdGgiOjUxMiwiaGVpZ2h0Ijo1MTJ9fSwiZmlsZU5hbWUiOiJzZW1pZmxha2UucG5nIiwiY29udGVudFR5cGUiOiJpbWFnZS9wbmcifX19fX0=')
76
- let halfcabModule = await import('./halfcab')
69
+ intialData('eyJjb250YWN0Ijp7InRpdGxlIjoiQ29udGFjdCBVcyJ9LCJyb3V0ZXIiOnsicGF0aG5hbWUiOiIvIn19')
70
+ let halfcabModule = await import('./halfcab.mjs')
77
71
  ;({
78
72
  ssr,
79
73
  html,
@@ -91,20 +85,21 @@ describe('halfcab', () => {
91
85
  halfcab = halfcabModule.default
92
86
  })
93
87
 
94
- it('Produces an HTML element when rendering', () => {
88
+ it('Produces a TemplateResult when rendering', () => {
95
89
  let el = html`
96
- <div oninput=${() => {
90
+ <div @input=${() => {
97
91
  }}></div>
98
92
  `
99
- expect(el instanceof HTMLDivElement).to.be.true()
93
+ // Check for Lit TemplateResult marker
94
+ expect(el['_$litType$']).to.exist()
100
95
  })
101
96
 
102
- it('Produces an HTML element wrapping as a reusable component', () => {
97
+ it('Produces a TemplateResult wrapping as a reusable component', () => {
103
98
  let el = args => html`
104
- <div oninput=${() => {
99
+ <div @input=${() => {
105
100
  }}></div>
106
101
  `
107
- expect(el({}) instanceof HTMLDivElement).to.be.true()
102
+ expect(el({})['_$litType$']).to.exist()
108
103
  })
109
104
 
110
105
  it('Runs halfcab function without error', () => {
@@ -114,13 +109,14 @@ describe('halfcab', () => {
114
109
  return html `<div></div>`
115
110
  }
116
111
  })
117
- .then(rootEl => {
112
+ .then(({rootEl}) => {
118
113
  expect(typeof rootEl === 'object').to.be.true()
119
114
  })
120
115
  })
121
116
 
122
117
  it('updating state causes a rerender with state', (done) => {
123
118
  halfcab({
119
+ el: '#root', // Ensure el is passed so rootEl is the container
124
120
  components (args) {
125
121
  return html`<div>${args.testing || ''}</div>`
126
122
  }
@@ -137,6 +133,7 @@ describe('halfcab', () => {
137
133
 
138
134
  it('updates state without merging arrays when told to', () => {
139
135
  return halfcab({
136
+ el: '#root',
140
137
  components () {
141
138
  return html `<div></div>`
142
139
  }
@@ -163,6 +160,7 @@ describe('halfcab', () => {
163
160
  }
164
161
  `
165
162
  return halfcab({
163
+ el: '#root',
166
164
  components (args) {
167
165
  return html `<div class="${style.myStyle}">${args.testing.inner || ''}</div>`
168
166
  }
@@ -178,6 +176,7 @@ describe('halfcab', () => {
178
176
 
179
177
  it('injects external content without error', () => {
180
178
  return halfcab({
179
+ el: '#root',
181
180
  components (args) {
182
181
  return html `<div>${injectMarkdown('### Heading')}</div>`
183
182
  }
@@ -190,6 +189,7 @@ describe('halfcab', () => {
190
189
 
191
190
  it('injects markdown without wrapper without error', () => {
192
191
  return halfcab({
192
+ el: '#root',
193
193
  components (args) {
194
194
  return html `<div>${injectMarkdown('### Heading', {wrapper: false})}</div>`
195
195
  }
@@ -362,6 +362,7 @@ describe('halfcab', () => {
362
362
  })
363
363
 
364
364
  return halfcab({
365
+ el: '#root',
365
366
  components () {
366
367
  return html `<div></div>`
367
368
  }
@@ -371,8 +372,7 @@ describe('halfcab', () => {
371
372
  let routing = () => {
372
373
  gotoRoute('/testFakeRoute')
373
374
  }
374
- expect(routing).to.not.throw()//made it out of the try catch, must
375
- // be fine
375
+ expect(routing).to.not.throw()
376
376
  })
377
377
 
378
378
  })
@@ -380,6 +380,7 @@ describe('halfcab', () => {
380
380
  it(`Throws an error when a route doesn't exist`, () => {
381
381
 
382
382
  return halfcab({
383
+ el: '#root',
383
384
  components () {
384
385
  return html `<div></div>`
385
386
  }
@@ -388,8 +389,7 @@ describe('halfcab', () => {
388
389
  let routing = () => {
389
390
  gotoRoute('/thisIsAFakeRoute')
390
391
  }
391
- expect(routing).to.throw()//made it out of the try catch, must be
392
- // fine
392
+ expect(routing).to.throw()
393
393
  })
394
394
 
395
395
  })
@@ -405,6 +405,7 @@ describe('halfcab', () => {
405
405
 
406
406
  it(`Doesn't clone when merging`, (done) => {
407
407
  halfcab({
408
+ el: '#root',
408
409
  components () {
409
410
  return html `<div></div>`
410
411
  }