halfcab 15.0.0 → 15.0.2

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'
@@ -68,20 +69,88 @@ if (typeof window !== 'undefined') {
68
69
 
69
70
  let geb = new eventEmitter({state})
70
71
 
72
+ const stringsCache = new WeakMap()
73
+
71
74
  let html = (strings, ...values) => {
72
- // fix for allowing csjs to coexist with nanohtml SSR
75
+ // fix for allowing csjs to coexist with lit-html
73
76
  values = values.map(value => {
74
- if (value && value.hasOwnProperty('toString')) {
75
- return value.toString()
77
+ if (value && value.hasOwnProperty('toString') && !value.hasOwnProperty('_$litType$')) {
78
+ // Check if it's a template result (lit-html object). If not, and has toString (like CSJS object), stringify it.
79
+ if (Array.isArray(value)) return value;
80
+ if (typeof value === 'object' && value !== null) {
81
+ if (value['_$litType$'] !== undefined) return value; // It's a TemplateResult
82
+ // CSJS object:
83
+ if (value.toString && value.toString !== Object.prototype.toString) {
84
+ return value.toString()
85
+ }
86
+ }
76
87
  }
77
88
  return value
78
89
  })
79
90
 
80
- return bel(strings, ...values)
91
+ // Conversion for onEvent=${fn} to @event=${fn}
92
+ let newStrings = stringsCache.get(strings)
93
+ if (!newStrings) {
94
+ const newRaw = strings.raw ? [...strings.raw] : [...strings]
95
+ const newVals = [...strings]
96
+ const onEventRegex = /on([a-zA-Z]+)=$/
97
+
98
+ for (let i = 0; i < newVals.length; i++) {
99
+ let match = newVals[i].match(onEventRegex)
100
+ if (match) {
101
+ const eventName = match[1]
102
+ const replacement = `@${eventName}=`
103
+ newVals[i] = newVals[i].replace(onEventRegex, replacement)
104
+ newRaw[i] = newRaw[i].replace(onEventRegex, replacement)
105
+ }
106
+ }
107
+
108
+ newStrings = newVals
109
+ newStrings.raw = newRaw
110
+ stringsCache.set(strings, newStrings)
111
+ }
112
+
113
+ return litHtml(newStrings, ...values)
114
+ }
115
+
116
+ // Detect if a container likely contains Lit SSR markers so hydration is safe
117
+ function canHydrateContainer (container) {
118
+ try {
119
+ if (!container || !container.hasChildNodes()) return false
120
+ // Walk comment nodes looking for lit markers inserted by @lit-labs/ssr
121
+ const walker = document.createTreeWalker(
122
+ container,
123
+ NodeFilter.SHOW_COMMENT,
124
+ null,
125
+ false
126
+ )
127
+ let n = walker.nextNode()
128
+ while (n) {
129
+ const data = (n.data || '').toLowerCase()
130
+ if (
131
+ data.includes('lit-part') ||
132
+ data.includes('lit$') ||
133
+ data.includes('lit-ssr')
134
+ ) {
135
+ return true
136
+ }
137
+ n = walker.nextNode()
138
+ }
139
+ } catch (e) {
140
+ // If anything goes wrong, err on the safe side and do not hydrate
141
+ return false
142
+ }
143
+ return false
81
144
  }
82
145
 
83
146
  function ssr (rootComponent) {
84
- let componentsString = `${rootComponent}`
147
+ // Use @lit-labs/ssr render
148
+ // It returns an iterable
149
+ const resultIterator = renderSSR(rootComponent)
150
+ let componentsString = ''
151
+ for (const chunk of resultIterator) {
152
+ componentsString += chunk
153
+ }
85
154
  return {componentsString, stylesString: componentCSSString}
86
155
  }
87
156
 
@@ -243,11 +312,12 @@ function nextTick (func) {
243
312
  function stateUpdated () {
244
313
  if (rootEl) {
245
314
  let startTime = Date.now()
246
- let newEl = components(state)
315
+ let newTemplate = components(state)
247
316
  console.log(`Component render: ${Date.now() - startTime}`)
248
317
  startTime = Date.now()
249
- update(rootEl, newEl)
250
- console.log(`DOM morph: ${Date.now() - startTime}`)
318
+ // Render into the container (rootEl)
319
+ render(newTemplate, rootEl)
320
+ console.log(`DOM update: ${Date.now() - startTime}`)
251
321
  }
252
322
  }
253
323
 
@@ -273,7 +343,8 @@ function updateState (updateObject, options) {
273
343
 
274
344
  debounce(stateUpdated)
275
345
 
276
- if (process.env.NODE_ENV !== 'production') {
346
+ // Avoid referencing process in browsers without a bundler (process is undefined)
347
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production') {
277
348
  console.log('------STATE UPDATE------')
278
349
  console.log(updateObject)
279
350
  console.log(' ')
@@ -286,31 +357,20 @@ function updateState (updateObject, options) {
286
357
  }
287
358
 
288
359
  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
- })
360
+ // This was for nanomorph. Lit handles updates differently.
361
+ // If we need to manipulate DOM before render, it's harder with Templates.
362
+ // Leaving empty or deprecated.
303
363
  }
304
364
 
305
365
  function injectHTML (htmlString, options) {
306
366
  if (options && options.wrapper === false) {
307
367
  return html([htmlString])
308
368
  }
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
369
+ return html([`<div>${htmlString}</div>`])
310
370
  }
311
371
 
312
372
  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
373
+ return injectHTML(decode(marked(mdString)), options)
314
374
  }
315
375
 
316
376
  function gotoRoute (route) {
@@ -422,18 +482,30 @@ export default (config, {shiftyRouter = shiftyRouterModule, href = hrefModule, h
422
482
  gotoRoute(location.href)
423
483
  })
424
484
 
425
- let c = components(state)//root element generated by components
485
+ let c = components(state)// component template
426
486
  if (el) {
427
-
428
- emptySSRVideos(c)
429
-
430
- let r = document.querySelector(el)
431
- rootEl = update(r, c)
487
+ // rootEl is the container
488
+ rootEl = document.querySelector(el)
489
+
490
+ // Initial render. Only hydrate when container has Lit SSR markers.
491
+ if (canHydrateContainer(rootEl)) {
492
+ try {
493
+ hydrate(c, rootEl)
494
+ } catch (e) {
495
+ // Fallback to render if hydration fails (or if not SSR'd by Lit)
496
+ console.warn('Hydration failed or not applicable, falling back to render', e)
497
+ render(c, rootEl)
498
+ }
499
+ } else {
500
+ render(c, rootEl)
501
+ }
502
+
432
503
  return resolve({rootEl, state})
433
504
  }
434
- rootEl = c
435
- resolve({rootEl, state})//if no root element provided, just return the root
436
- // component and the state
505
+ // If no root element provided?
506
+ rootEl = null
507
+ // We return 'c' which is now a TemplateResult.
508
+ resolve({rootEl: c, state})
437
509
  })
438
510
  }
439
511
 
@@ -486,6 +558,7 @@ export {
486
558
  html,
487
559
  defineRoute,
488
560
  updateState,
561
+ state,
489
562
  formField,
490
563
  gotoRoute,
491
564
  cssTag as css,
@@ -499,4 +572,4 @@ export {
499
572
  LRU,
500
573
  cachedComponent,
501
574
  PureComponent
502
- }
575
+ }
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "halfcab",
3
- "version": "15.0.0",
3
+ "version": "15.0.2",
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,10 @@
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
62
  "nanocomponent": "^6.5.2",
59
- "nanohtml": "^1.6.3",
60
63
  "nanolru": "^1.0.0",
61
- "nanomorph": "^5.4.0",
62
64
  "qs": "^6.5.2",
63
65
  "shifty-router": "^0.1.1"
64
66
  },
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
  }