UNPKG

halfcab

Version:

A simple universal JavaScript framework focused on making use of es2015 template strings to build components.

441 lines (390 loc) 11.2 kB
import chai from 'chai' import dirtyChai from 'dirty-chai' import sinon from 'sinon' import sinonChai from 'sinon-chai' import jsdomGlobal from 'jsdom-global' const {expect} = chai chai.use(dirtyChai) chai.use(sinonChai) chai.use(dirtyChai) let halfcab, ssr, html, defineRoute, gotoRoute, formField, cache, updateState, injectMarkdown, formIsValid, css, state, getRouteComponent, nextTick function intialData (dataInitial) { let el = document.createElement('div') el.setAttribute('data-initial', dataInitial) document.body.appendChild(el) // Also ensure a root element exists for tests that target #root let root = document.createElement('div') root.id = 'root' document.body.appendChild(root) } describe('halfcab', () => { describe('Server', () => { before(async () => { jsdomGlobal() intialData('eyJjb250YWN0Ijp7InRpdGxlIjoiQ29udGFjdCBVcyJ9LCJyb3V0ZXIiOnsicGF0aG5hbWUiOiIvIn19') let halfcabModule = await import('./halfcab.mjs') ;({ ssr, html, defineRoute, gotoRoute, formField, cache, updateState, injectMarkdown, formIsValid, css, getRouteComponent, nextTick } = halfcabModule) halfcab = halfcabModule.default }) it('Produces a string when doing SSR', () => { let style = css` .myStyle { width: 100px; } ` // Use html instead of serverHtml (which depended on nanohtml) // lit-html on server via ssr() should return string let {componentsString, stylesString} = ssr(html` <div class="${style.myStyle}" @input=${() => { }}></div> `) expect(typeof componentsString === 'string').to.be.true() }) }) describe('Client', () => { before(async () => { jsdomGlobal() intialData('eyJjb250YWN0Ijp7InRpdGxlIjoiQ29udGFjdCBVcyJ9LCJyb3V0ZXIiOnsicGF0aG5hbWUiOiIvIn19') let halfcabModule = await import('./halfcab.mjs') ;({ ssr, html, defineRoute, gotoRoute, formField, cache, updateState, injectMarkdown, formIsValid, css, getRouteComponent, nextTick } = halfcabModule) halfcab = halfcabModule.default }) it('Produces a TemplateResult when rendering', () => { let el = html` <div @input=${() => { }}></div> ` // Check for Lit TemplateResult marker expect(el['_$litType$']).to.exist() }) it('Produces a TemplateResult wrapping as a reusable component', () => { let el = args => html` <div @input=${() => { }}></div> ` expect(el({})['_$litType$']).to.exist() }) it('Runs halfcab function without error', () => { return halfcab({ el: '#root', components () { return html `<div></div>` } }) .then(({rootEl}) => { expect(typeof rootEl === 'object').to.be.true() }) }) it('updating state causes a rerender with state', (done) => { halfcab({ el: '#root', // Ensure el is passed so rootEl is the container components (args) { return html`<div>${args.testing || ''}</div>` } }) .then(({rootEl, state}) => { updateState({testing: 'works'}) nextTick(()=> { expect(rootEl.innerHTML.includes('works')).to.be.true() done() }) }) }) it('updates state without merging arrays when told to', () => { return halfcab({ el: '#root', components () { return html `<div></div>` } }) .then(({rootEl, state}) => { updateState({ myArray: ['1', '2', '3'] }) updateState({ myArray: ['4'] }, { arrayMerge: false }) expect(state.myArray.length).to.equal(1) }) }) it('updating state without deepmerge overwrites objects', () => { var style = css` .myStyle { width: 100px; } ` return halfcab({ el: '#root', components (args) { return html `<div class="${style.myStyle}">${args.testing.inner || ''}</div>` } }) .then(({rootEl, state}) => { updateState({testing: {inner: 'works'}}) updateState({testing: {inner2: 'works'}}, { deepMerge: false }) expect(rootEl.innerHTML.indexOf('works')).to.equal(-1) }) }) it('injects external content without error', () => { return halfcab({ el: '#root', components (args) { return html `<div>${injectMarkdown('### Heading')}</div>` } }) .then(({rootEl, state}) => { expect(rootEl.innerHTML.indexOf('###')).to.equal(-1) expect(rootEl.innerHTML.indexOf('<h3')).not.to.equal(-1) }) }) it('injects markdown without wrapper without error', () => { return halfcab({ el: '#root', components (args) { return html `<div>${injectMarkdown('### Heading', {wrapper: false})}</div>` } }) .then(({rootEl, state}) => { expect(rootEl.innerHTML.indexOf('###')).to.equal(-1) expect(rootEl.innerHTML.indexOf('<h3')).not.to.equal(-1) }) }) describe('formField', () => { it('Returns a function', () => { var holdingPen = {} var output = formField(holdingPen, 'test') expect(typeof output === 'function').to.be.true() }) it('Sets a property within the valid object of the same name', () => { var holdingPen = {} var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'text', validity: { valid: false } } } output(e) let validFound Object.getOwnPropertySymbols(holdingPen).forEach(symb => { if (symb.toString().indexOf('Symbol(valid)') === 0 && holdingPen[symb] !== undefined) { validFound = symb } }) expect(validFound).to.exist() }) it('Runs OK if a valid object is already present', () => { var holdingPen = {valid: {}} var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'text', validity: { valid: false } } } output(e) expect(holdingPen.valid.test).to.exist() }) it('Sets checkboxes without error', () => { var holdingPen = {} var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'checkbox', validity: { valid: false }, checked: true } } output(e) let validFound Object.getOwnPropertySymbols(holdingPen).forEach(symb => { if (symb.toString().indexOf('Symbol(valid)') === 0 && holdingPen[symb] !== undefined) { validFound = symb } }) expect(validFound).to.exist() }) it('Sets radio buttons without error', () => { var holdingPen = {} var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'radio', validity: { valid: false }, checked: true } } output(e) let validFound Object.getOwnPropertySymbols(holdingPen).forEach(symb => { if (symb.toString().indexOf('Symbol(valid)') === 0 && holdingPen[symb] !== undefined) { validFound = symb } }) expect(validFound).to.exist() }) it('Validates a form without error', () => { var holdingPen = { test: '', [Symbol('valid')]: { test: false } } var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'radio', validity: { valid: true }, checked: true } } output(e) expect(formIsValid(holdingPen)).to.be.true() }) it('Validates when valid object already present', () => { var holdingPen = { test: '', [Symbol('valid')]: { test: false }, valid: {} } var output = formField(holdingPen, 'test') var e = { currentTarget: { type: 'radio', validity: { valid: true }, checked: true } } output(e) expect(formIsValid(holdingPen)).to.be.true() }) }) describe('routing', () => { let windowStub after(() => { windowStub.restore() }) before(() => { windowStub = sinon.stub(window.history, 'pushState') }) it('Makes the route available when using defineRoute', () => { defineRoute({ path: '/testFakeRoute', title: 'Report Pal', callback: output => { updateState({ showContact: true }) } }) return halfcab({ el: '#root', components () { return html `<div></div>` } }) .then(rootEl => { let routing = () => { gotoRoute('/testFakeRoute') } expect(routing).to.not.throw() }) }) it(`Throws an error when a route doesn't exist`, () => { return halfcab({ el: '#root', components () { return html `<div></div>` } }) .then(rootEl => { let routing = () => { gotoRoute('/thisIsAFakeRoute') } expect(routing).to.throw() }) }) }) it('has initial data injects router when its not there to start with', () => { defineRoute({path: '/routeWithComponent', component: {fakeComponent: true}}) expect(getRouteComponent('/routeWithComponent').fakeComponent) .to .be .true() }) it(`Doesn't clone when merging`, (done) => { halfcab({ el: '#root', components () { return html `<div></div>` } }) .then(({rootEl, state}) => { let myObject = { test: 1, fake: 'String2' } updateState({ myObject }) nextTick(() => { state.myObject.test = 2 updateState({ myOtherObject: { test: 1, fake: 'String2' } }) nextTick(() => { expect(state.myObject.test).to.equal(2) expect(myObject.test).to.equal(2) done() }, 20) }) }) }) }) })