diff --git a/.npmignore b/.npmignore index 7dbef3d..68d4d15 100644 --- a/.npmignore +++ b/.npmignore @@ -25,6 +25,5 @@ tags **.ts !**.d.ts **.spec.* -**.map **.json .* diff --git a/LICENSE.md b/LICENSE.md index 1f5e1cd..b7f32b6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 Tomek Łaziuk +Copyright (c) 2019 Tomek Łaziuk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/index.spec.ts b/index.spec.ts index 3b80145..3b4895f 100644 --- a/index.spec.ts +++ b/index.spec.ts @@ -1,268 +1,122 @@ // tslint:disable:no-reference max-line-length max-classes-per-file -/// - -import { - expect, -} from "chai"; - -import { - SinonSpy, - spy, -} from "sinon"; - -import { - Attributes, - ClassComponent, - Component, - CVnode, - FactoryComponent, - Lifecycle, - Vnode, -} from "mithril"; - -import * as m from "mithril/render/hyperscript"; -import * as mTrust from "mithril/render/trust"; - -import render, { - Escape as EscapeEnum, - isClassComponent, - isComponent, - isComponentType, - isFactoryComponent, -} from "./index"; + +import { expect } from "chai"; +import { Attributes, ClassComponent, Component, CVnode, FactoryComponent, Vnode } from "mithril"; +// @ts-ignore no type defs +import m from "mithril/render/hyperscript"; +// @ts-ignore no type defs +import mTrust from "mithril/render/trust"; +import { SinonSpy, spy } from "sinon"; +import render, { isClassComponent, isComponent, isComponentType, isFactoryComponent } from "./index"; describe(render.name, () => { describe(`node`, () => { - it(`should render tag`, async (done) => { - try { - expect(await render(m(`span`, `content`))).to.be.equal(`content`); - done(); - } catch (err) { - done(err); - } + it(`should render tag`, async () => { + expect(await render(m(`span`, `content`))).to.be.equal(`content`); }); - it(`should render classname`, async (done) => { - try { - expect(await render(m(`.foo`, `content`))).to.be.equal(`
content
`); - done(); - } catch (err) { - done(err); - } + it(`should render classname`, async () => { + expect(await render(m(`.foo`, `content`))).to.be.equal(`
content
`); }); - it(`should render id`, async (done) => { - try { - expect(await render(m(`#bar`, `content`))).to.be.equal(`
content
`); - done(); - } catch (err) { - done(err); - } + it(`should render id`, async () => { + expect(await render(m(`#bar`, `content`))).to.be.equal(`
content
`); }); - it(`should render short nodes when no children`, async (done) => { - try { - expect(await render(m(`br`))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render short nodes when no children`, async () => { + expect(await render(m(`br`))).to.be.equal(`
`); }); - it(`should render short nodes when no children and tag name is uppercase`, async (done) => { - try { - expect(await render(m(`HR`))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render short nodes when no children and tag name is uppercase`, async () => { + expect(await render(m(`HR`))).to.be.equal(`
`); }); - it(`should render short node doctype`, async (done) => { - try { - expect(await render(m(`!doctype`))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render short node doctype`, async () => { + expect(await render(m(`!doctype`))).to.be.equal(``); }); - it(`should render short node doctype HTML5`, async (done) => { - try { - expect(await render(m(`!doctype`, { html: true }))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render short node doctype HTML5`, async () => { + expect(await render(m(`!doctype`, { html: true }))).to.be.equal(``); }); - it(`should render attributes`, async (done) => { - try { - expect(await render(m(`span`, { "data-foo": `bar`, "selected": `selected` }))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render attributes`, async () => { + expect(await render(m(`span`, { "data-foo": `bar`, "selected": `selected` }))).to.be.equal(``); }); - it(`should render string`, async (done) => { - try { - expect(await render(m(`ul`, `huhu`))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render string`, async () => { + expect(await render(m(`ul`, `huhu`))).to.be.equal(``); }); - it(`should render arrays`, async (done) => { - try { - expect(await render([m(`span`, `foo`), m(`div`, `bar`)])).to.be.equal(`foo
bar
`); - done(); - } catch (err) { - done(err); - } + it(`should render arrays`, async () => { + expect(await render([m(`span`, `foo`), m(`div`, `bar`)])).to.be.equal(`foo
bar
`); }); - it(`should render nested arrays`, async (done) => { - try { - expect(await render(m(`div`, [[m(`span`, `foo`), m(`div`, `bar`)]]))).to.be.equal(`
foo
bar
`); - done(); - } catch (err) { - done(err); - } + it(`should render nested arrays`, async () => { + expect(await render(m(`div`, [[m(`span`, `foo`), m(`div`, `bar`)]]))).to.be.equal(`
foo
bar
`); }); - it(`should render children`, async (done) => { - try { - expect(await render(m(`span`, m(`div`)))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render children`, async () => { + expect(await render(m(`span`, m(`div`)))).to.be.equal(`
`); }); - it(`should not render events`, async (done) => { - try { - expect(await render(m(`span`, { onmousemove: () => void 0 }))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should not render events`, async () => { + expect(await render(m(`span`, { onmousemove: () => void 0 }))).to.be.equal(``); }); - it(`should render children`, async (done) => { - try { - expect(await render(m(`span`, { style: { paddingLeft: `10px`, color: `red` } }))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render children`, async () => { + expect(await render(m(`span`, { style: { paddingLeft: `10px`, color: `red` } }))).to.be.equal(``); }); - it(`should render numbers as text nodes`, async (done) => { - try { - expect(await render(m(`div`, [1, m(`span`), `2`]))).to.be.equal(`
12
`); - expect(await render(m(`div`, 1))).to.be.equal(`
1
`); - done(); - } catch (err) { - done(err); - } + it(`should render numbers as text nodes`, async () => { + expect(await render(m(`div`, [1, m(`span`), `2`]))).to.be.equal(`
12
`); + expect(await render(m(`div`, 1))).to.be.equal(`
1
`); }); - it(`should render booleans as text nodes`, async (done) => { - try { - expect(await render(m(`div`, false))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render booleans as text nodes`, async () => { + expect(await render(m(`div`, false))).to.be.equal(`
`); }); - it(`should render boolean attributes`, async (done) => { - try { - expect(await render(m(`div`, { a: true }))).to.be.equal(`
`); - expect(await render(m(`div`, { a: false }))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render boolean attributes`, async () => { + expect(await render(m(`div`, { a: true }))).to.be.equal(`
`); + expect(await render(m(`div`, { a: false }))).to.be.equal(`
`); }); - it(`should not render empty attributes`, async (done) => { - try { - expect(await render(m(`div`, { a: void 0 }))).to.be.equal(`
`); - expect(await render(m(`div`, { style: null }))).to.be.equal(`
`); - expect(await render(m(`div`, { style: `` }))).to.be.equal(`
`); - expect(await render(m(`div`, { style: { color: `` } }))).to.be.equal(`
`); - expect(await render(m(`div`, { style: { height: `20px`, color: `` } }))).to.be.equal(`
`); - expect(await render(m(`div`, { style: { height: `20px`, color: ``, width: `10px` } }))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should not render empty attributes`, async () => { + expect(await render(m(`div`, { a: void 0 }))).to.be.equal(`
`); + expect(await render(m(`div`, { style: null }))).to.be.equal(`
`); + expect(await render(m(`div`, { style: `` }))).to.be.equal(`
`); + expect(await render(m(`div`, { style: { color: `` } }))).to.be.equal(`
`); + expect(await render(m(`div`, { style: { height: `20px`, color: `` } }))).to.be.equal(`
`); + expect(await render(m(`div`, { style: { height: `20px`, color: ``, width: `10px` } }))).to.be.equal(`
`); }); - it(`should render custom attributes`, async (done) => { - try { - expect(await render(m(`div`, { a: `foo` }))).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render custom attributes`, async () => { + expect(await render(m(`div`, { a: `foo` }))).to.be.equal(`
`); }); - it(`should escape HTML`, async (done) => { - try { - expect(await render(m(`div`, mTrust(``)))).to.be.equal(`
`, `trusted html escaped`); - expect(await render(m(`div`, ``))).to.be.equal(`
<foo></foo>
`, `non-trusted html not escaped`); - expect(await render(m(`div`, { style: `">
`, `attribute value not escaped`); - expect(await render(m(`pre`, `var = ${JSON.stringify({ foo: 1 })}`))).to.be.equal(`
var = {"foo":1}
`, `non-html text escaped`); - done(); - } catch (err) { - done(err); - } + it(`should escape HTML`, async () => { + expect(await render(m(`div`, mTrust(``)))).to.be.equal(`
`, `trusted html escaped`); + expect(await render(m(`div`, ``))).to.be.equal(`
<foo></foo>
`, `non-trusted html not escaped`); + expect(await render(m(`div`, { style: `">
`, `attribute value not escaped`); + expect(await render(m(`pre`, `var = ${JSON.stringify({ foo: 1 })}`))).to.be.equal(`
var = {"foo":1}
`, `non-html text escaped`); }); - it(`should render svg use elements with href attributes`, async (done) => { - try { - expect(await render(m(`svg`, m(`use`, { href: `fooga.com` })))).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render svg use elements with href attributes`, async () => { + expect(await render(m(`svg`, m(`use`, { href: `fooga.com` })))).to.be.equal(``); }); - it(`should render closed input-tag`, async (done) => { - try { - expect(await render(m(`input`), { strict: true })).to.be.equal(``); - done(); - } catch (err) { - done(err); - } + it(`should render closed input-tag`, async () => { + expect(await render(m(`input`), { strict: true })).to.be.equal(``); }); - it(`should render closed div-tag`, async (done) => { - try { - expect(await render(m(`div`), { strict: true })).to.be.equal(`
`); - done(); - } catch (err) { - done(err); - } + it(`should render closed div-tag`, async () => { + expect(await render(m(`div`), { strict: true })).to.be.equal(`
`); }); - it(`should throw on non-component`, async (done) => { - try { - try { - await render(Symbol() as any); - done(new Error(`failed`)); - } catch { - // pass - } - done(); - } catch (err) { - done(err); - } - }); + it(`should throw on non-component`, () => render(Symbol()).then( + () => { throw new Error(`failed`); }, + () => void 0, + )); }); describe(`lifecycle`, () => { @@ -276,450 +130,350 @@ describe(render.name, () => { viewSpy = spy(({ children }: Vnode = {} as any) => children); }); - it(`node`, async (done) => { - try { - await render(m( - `div`, - { - oninit: oninitSpy, - onremove: onremoveSpy, - } as Attributes, - [ - `test`, - ], - )); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`'attrs' over 'state'`, async (done) => { - let oninitAttrsSpy = spy(); - let cmp = { + it(`node`, async () => { + await render(m( + `div`, + { + oninit: oninitSpy, + onremove: onremoveSpy, + } as Attributes, + [ + `test`, + ], + )); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + }); + + it(`'attrs' over 'state'`, async () => { + const oninitAttrsSpy = spy(); + const cmp = { oninit: oninitSpy, onremove: onremoveSpy, view: viewSpy, } as Component; - try { - await (render(m(cmp, { oninit: oninitAttrsSpy }))); - expect(oninitSpy).to.have.property(`callCount`).equal(0, `state lifecycle method called`); - expect(oninitAttrsSpy).to.have.property(`callCount`).gt(0, `attrs lifecycle method not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`text`, async (done) => { - try { - await render(m( - `#`, - { - oninit: oninitSpy, - onremove: onremoveSpy, - } as Attributes, - [ - `test`, - ], - )); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`fragment`, async (done) => { - try { - await render(m( - `[`, - { - oninit: oninitSpy, - onremove: onremoveSpy, - } as Attributes, - [ - `test`, - ], - )); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`trusted html`, async (done) => { - try { - await render(m( - `<`, - { - oninit: oninitSpy, - onremove: onremoveSpy, - } as Attributes, - [ - `
test
`, - ], - )); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`Component`, async (done) => { - try { - await render(m( - { - oninit: oninitSpy, - onremove: onremoveSpy, - view: viewSpy, - } as Component, - [ - `test`, - ], - )); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`FactoryComponent`, async (done) => { - try { - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - await render(m(function() { - return { - oninit: oninitSpy, - onremove: onremoveSpy, - view: viewSpy, - }; - } as FactoryComponent)); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`ClassComponent`, async (done) => { - try { - class Cmp implements ClassComponent { - public oninit = oninitSpy; - public onremove = onremoveSpy; - // important note - view method can not be assigned in constructor, - // view method is used to detect if function is a class, - // which means it's impossible to do following thing: - // public view = viewSpy; - public view() { - viewSpy(); - }; + await (render(m(cmp, { oninit: oninitAttrsSpy }))); + expect(oninitSpy).to.have.property(`callCount`).equal(0, `state lifecycle method called`); + expect(oninitAttrsSpy).to.have.property(`callCount`).gt(0, `attrs lifecycle method not called`); + }); + + it(`text`, async () => { + await render(m( + `#`, + { + oninit: oninitSpy, + onremove: onremoveSpy, + } as Attributes, + [ + `test`, + ], + )); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + }); + + it(`fragment`, async () => { + await render(m( + `[`, + { + oninit: oninitSpy, + onremove: onremoveSpy, + } as Attributes, + [ + `test`, + ], + )); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + }); + + it(`trusted html`, async () => { + await render(m( + `<`, + { + oninit: oninitSpy, + onremove: onremoveSpy, + } as Attributes, + [ + `
test
`, + ], + )); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + }); + + it(`Component`, async () => { + await render(m( + { + oninit: oninitSpy, + onremove: onremoveSpy, + view: viewSpy, + } as Component, + [ + `test`, + ], + )); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); + }); + + it(`FactoryComponent`, async () => { + // tslint:disable-next-line:only-arrow-functions object-literal-shorthand + await render(m((() => ({ + oninit: oninitSpy, + onremove: onremoveSpy, + view: viewSpy, + })) as FactoryComponent)); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); + }); + + it(`ClassComponent`, async () => { + class Cmp implements ClassComponent { + public oninit = oninitSpy; + public onremove = onremoveSpy; + // important note - view method can not be assigned in constructor, + // view method is used to detect if function is a class, + // which means it's impossible to do following thing: + // public view = viewSpy; + public view() { + viewSpy(); } - await render(m(Cmp)); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - done(); - } catch (err) { - done(err); - } - }); - - it(`should have proper call order`, async (done) => { - try { - const oninitRootSpy = spy(); - const onremoveRootSpy = spy(); - const oninitFragmentSpy = spy(); - const onremoveFragmentSpy = spy(); - const oninitCmp1Spy = spy(); - const onremoveCmp1Spy = spy(); - const oninitCmp2Spy = spy(); - const onremoveCmp2Spy = spy(); - const cmpRoot = { - oninit: oninitRootSpy, - onremove: onremoveRootSpy, - view: () => [ - `root`, - m( - `[`, - { - oninit: oninitFragmentSpy, - onremove: onremoveFragmentSpy, - }, - [ - m( - `#`, - { - oninit: oninitCmp1Spy, - onremove: onremoveCmp1Spy, - }, - `fragment`, - ), - ], - ), - m( - `#`, - { - oninit: oninitCmp2Spy, - onremove: onremoveCmp2Spy, - }, - `cmp`, - ), - ], - } as Component; - expect(await render(m(cmpRoot))).to.be.equal(`root
fragment
cmp
`); - - expect(oninitRootSpy.calledOnce).to.be.equal(true, `oninit root not called`); - expect(onremoveRootSpy.calledOnce).to.be.equal(true, `onremove root not called`); - expect(oninitFragmentSpy.calledOnce).to.be.equal(true, `oninit fragment not called once`); - expect(onremoveFragmentSpy.calledOnce).to.be.equal(true, `onremove fragment not called once`); - expect(oninitCmp1Spy.calledOnce).to.be.equal(true, `oninit cmp1 not called once`); - expect(onremoveCmp1Spy.calledOnce).to.be.equal(true, `onremove cmp1 not called once`); - expect(oninitCmp2Spy.calledOnce).to.be.equal(true, `oninit cmp2 not called once`); - expect(onremoveCmp2Spy.calledOnce).to.be.equal(true, `onremove cmp2 not called once`); - - expect(oninitFragmentSpy.calledBefore(oninitCmp2Spy)).to.be.equal(true, `oninit fragment not called before oninit cmp2`); - expect(oninitRootSpy.calledBefore(oninitFragmentSpy)).to.be.equal(true, `oninit root not called before oninit fragment`); - expect(onremoveCmp1Spy.calledAfter(oninitCmp1Spy)).to.be.equal(true, `onremove not called after oninit`); - expect(onremoveCmp2Spy.calledAfter(oninitCmp2Spy)).to.be.equal(true, `onremove not called after oninit`); - expect(onremoveFragmentSpy.calledAfter(oninitFragmentSpy)).to.be.equal(true, `onremove not called after oninit`); - expect(onremoveFragmentSpy.calledAfter(onremoveCmp2Spy)).to.be.equal(true, `onremove fragment not called after onremove cmp2`); - expect(onremoveRootSpy.calledAfter(oninitRootSpy)).to.be.equal(true, `onremove not called after oninit`); - expect(onremoveRootSpy.calledAfter(onremoveFragmentSpy)).to.be.equal(true, `onremove root not called after onremove fragment`); - done(); - } catch (err) { - done(err); } + await render(m(Cmp)); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); + }); + + it(`should have proper call order`, async () => { + const oninitRootSpy = spy(); + const onremoveRootSpy = spy(); + const oninitFragmentSpy = spy(); + const onremoveFragmentSpy = spy(); + const oninitCmp1Spy = spy(); + const onremoveCmp1Spy = spy(); + const oninitCmp2Spy = spy(); + const onremoveCmp2Spy = spy(); + const cmpRoot = { + oninit: oninitRootSpy, + onremove: onremoveRootSpy, + view: () => [ + `root`, + m( + `[`, + { + oninit: oninitFragmentSpy, + onremove: onremoveFragmentSpy, + }, + [ + m( + `#`, + { + oninit: oninitCmp1Spy, + onremove: onremoveCmp1Spy, + }, + `fragment`, + ), + ], + ), + m( + `#`, + { + oninit: oninitCmp2Spy, + onremove: onremoveCmp2Spy, + }, + `cmp`, + ), + ], + } as Component; + expect(await render(m(cmpRoot))).to.be.equal(`root
fragment
cmp
`); + + expect(oninitRootSpy.calledOnce).to.be.equal(true, `oninit root not called`); + expect(onremoveRootSpy.calledOnce).to.be.equal(true, `onremove root not called`); + expect(oninitFragmentSpy.calledOnce).to.be.equal(true, `oninit fragment not called once`); + expect(onremoveFragmentSpy.calledOnce).to.be.equal(true, `onremove fragment not called once`); + expect(oninitCmp1Spy.calledOnce).to.be.equal(true, `oninit cmp1 not called once`); + expect(onremoveCmp1Spy.calledOnce).to.be.equal(true, `onremove cmp1 not called once`); + expect(oninitCmp2Spy.calledOnce).to.be.equal(true, `oninit cmp2 not called once`); + expect(onremoveCmp2Spy.calledOnce).to.be.equal(true, `onremove cmp2 not called once`); + + expect(oninitFragmentSpy.calledBefore(oninitCmp2Spy)).to.be.equal(true, `oninit fragment not called before oninit cmp2`); + expect(oninitRootSpy.calledBefore(oninitFragmentSpy)).to.be.equal(true, `oninit root not called before oninit fragment`); + expect(onremoveCmp1Spy.calledAfter(oninitCmp1Spy)).to.be.equal(true, `onremove not called after oninit`); + expect(onremoveCmp2Spy.calledAfter(oninitCmp2Spy)).to.be.equal(true, `onremove not called after oninit`); + expect(onremoveFragmentSpy.calledAfter(oninitFragmentSpy)).to.be.equal(true, `onremove not called after oninit`); + expect(onremoveFragmentSpy.calledAfter(onremoveCmp2Spy)).to.be.equal(true, `onremove fragment not called after onremove cmp2`); + expect(onremoveRootSpy.calledAfter(oninitRootSpy)).to.be.equal(true, `onremove not called after oninit`); + expect(onremoveRootSpy.calledAfter(onremoveFragmentSpy)).to.be.equal(true, `onremove root not called after onremove fragment`); }); }); describe(`attributes and state`, () => { - it(`Component`, async (done) => { - try { - interface IState { - text?: string; - } - const component = { - view: ({ attrs, state }) => { - return attrs.text || state.text || `test`; - }, - } as Component & IState; - expect(await render(m(component))).to.be.equal(`test`); - expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); - component.text = `state`; - expect(await render(m(component))).to.be.equal(`state`); - expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); - done(); - } catch (err) { - done(err); - } - }); - - it(`ClassComponent`, async (done) => { - try { - class Cmp implements ClassComponent { - public text?: string; - public view({ attrs, state }: CVnode) { - return attrs.text || this.text || `test`; - } + it(`Component`, async () => { + interface IState { + text?: string; + } + const component = { + view: ({ attrs, state }) => { + return attrs.text || state.text || `test`; + }, + } as Component & IState; + expect(await render(m(component))).to.be.equal(`test`); + expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); + component.text = `state`; + expect(await render(m(component))).to.be.equal(`state`); + expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); + }); + + it(`ClassComponent`, async () => { + class Cmp implements ClassComponent { + public text?: string; + public view({ attrs, state }: CVnode) { + return attrs.text || this.text || `test`; } - expect(await render(m(Cmp))).to.be.equal(`test`); - expect(await render(m(Cmp, { text: `attr` }))).to.be.equal(`attr`); - Cmp.prototype.text = `state`; - expect(await render(m(Cmp))).to.be.equal(`state`); - expect(await render(m(Cmp, { text: `attr` }))).to.be.equal(`attr`); - done(); - } catch (err) { - done(err); } + expect(await render(m(Cmp))).to.be.equal(`test`); + expect(await render(m(Cmp, { text: `attr` }))).to.be.equal(`attr`); + Cmp.prototype.text = `state`; + expect(await render(m(Cmp))).to.be.equal(`state`); + expect(await render(m(Cmp, { text: `attr` }))).to.be.equal(`attr`); }); - it(`FactoryComponent`, async (done) => { - try { - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - const component = function() { - return { - view: ({ attrs }) => { - return attrs.text || `test`; - }, - }; - } as FactoryComponent; - expect(await render(m(component))).to.be.equal(`test`); - expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); - done(); - } catch (err) { - done(err); - } + it(`FactoryComponent`, async () => { + // tslint:disable-next-line:only-arrow-functions object-literal-shorthand + const component = (() => ({ + view: ({ attrs }) => { + return attrs.text || `test`; + }, + })) as FactoryComponent; + expect(await render(m(component))).to.be.equal(`test`); + expect(await render(m(component, { text: `attr` }))).to.be.equal(`attr`); }); }); describe(`'this' in text`, () => { - it(`should 'state' be equal 'this'`, async (done) => { - try { - const oninitSpy = spy(); - const onremoveSpy = spy(); - await render(m(`div`, { - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - oninit: function({ state }) { - oninitSpy(); - expect(this).to.be.equal(state); - }, - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - onremove: function({ state }) { - onremoveSpy(); - expect(this).to.be.equal(state); - }, - } as Attributes)); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - done(); - } catch (err) { - done(err); - } + it(`should 'state' be equal 'this'`, async () => { + const oninitSpy = spy(); + const onremoveSpy = spy(); + await render(m(`div`, { + oninit({ state }) { + oninitSpy(); + expect(this).to.be.equal(state); + }, + onremove({ state }) { + onremoveSpy(); + expect(this).to.be.equal(state); + }, + } as Attributes)); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); }); }); describe(`'this' in Component`, () => { - it(`should 'state' be equal 'this'`, async (done) => { - try { - const oninitSpy = spy(); - const onremoveSpy = spy(); - const viewSpy = spy(); - await render(m({ - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - oninit: function({ state }) { - oninitSpy(); - expect(this).to.be.equal(state); - }, - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - onremove: function({ state }) { - onremoveSpy(); - expect(this).to.be.equal(state); - }, - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - view: function({ state }) { - viewSpy(); - expect(this).to.be.equal(state); - return ``; - }, - } as Component)); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - done(); - } catch (err) { - done(err); - } + it(`should 'state' be equal 'this'`, async () => { + const oninitSpy = spy(); + const onremoveSpy = spy(); + const viewSpy = spy(); + await render(m({ + oninit({ state }) { + oninitSpy(); + expect(this).to.be.equal(state); + }, + onremove({ state }) { + onremoveSpy(); + expect(this).to.be.equal(state); + }, + view({ state }) { + viewSpy(); + expect(this).to.be.equal(state); + return ``; + }, + } as Component)); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); }); }); describe(`'this' in FactoryComponent`, () => { - it(`should 'state' be equal 'this'`, async (done) => { - try { - const oninitSpy = spy(); - const onremoveSpy = spy(); - const viewSpy = spy(); - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - await render(m(function() { - return { - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - oninit: function({ state }) { - oninitSpy(); - expect(this).to.be.equal(state); - }, - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - onremove: function({ state }) { - onremoveSpy(); - expect(this).to.be.equal(state); - }, - // tslint:disable-next-line:only-arrow-functions object-literal-shorthand - view: function({ state }) { - viewSpy(); - expect(this).to.be.equal(state); - return ``; - }, - }; - } as FactoryComponent)); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - done(); - } catch (err) { - done(err); - } + it(`should 'state' be equal 'this'`, async () => { + const oninitSpy = spy(); + const onremoveSpy = spy(); + const viewSpy = spy(); + await render(m((() => ({ + oninit({ state }) { + oninitSpy(); + expect(this).to.be.equal(state); + }, + onremove({ state }) { + onremoveSpy(); + expect(this).to.be.equal(state); + }, + view({ state }) { + viewSpy(); + expect(this).to.be.equal(state); + return ``; + }, + })) as FactoryComponent)); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); }); }); describe(`'this' in ClassComponent`, () => { - it(`should 'state' be equal 'this'`, async (done) => { - try { - const oninitSpy = spy(); - const onremoveSpy = spy(); - const viewSpy = spy(() => ``); - class Cmp implements ClassComponent { - public get oninit() { - return oninitSpy; - } - public get onremove() { - return onremoveSpy; - } - public get view() { - return viewSpy; - } + it(`should 'state' be equal 'this'`, async () => { + const oninitSpy = spy(); + const onremoveSpy = spy(); + const viewSpy = spy(() => ``); + class Cmp implements ClassComponent { + public get oninit() { + return oninitSpy; + } + public get onremove() { + return onremoveSpy; + } + public get view() { + return viewSpy; } - expect(await render(m(Cmp))).to.be.equal(``); - expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); - expect(viewSpy.firstCall.thisValue).to.be.equal(viewSpy.firstCall.args[0].state, `view was not with proper 'this'`); - expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); - expect(onremoveSpy.firstCall.thisValue).to.be.equal(onremoveSpy.firstCall.args[0].state, `onremove was not with proper 'this'`); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(oninitSpy.firstCall.thisValue).to.be.equal(oninitSpy.firstCall.args[0].state, `oninit was not with proper 'this'`); - done(); - } catch (err) { - done(err); } + expect(await render(m(Cmp))).to.be.equal(``); + expect(viewSpy.calledOnce).to.be.equal(true, `view was not called`); + expect(viewSpy.firstCall.thisValue).to.be.equal(viewSpy.firstCall.args[0].state, `view was not with proper 'this'`); + expect(onremoveSpy.calledOnce).to.be.equal(true, `onremove was not called`); + expect(onremoveSpy.firstCall.thisValue).to.be.equal(onremoveSpy.firstCall.args[0].state, `onremove was not with proper 'this'`); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(oninitSpy.firstCall.thisValue).to.be.equal(oninitSpy.firstCall.args[0].state, `oninit was not with proper 'this'`); }); }); describe(`async`, () => { - it(`oninit`, async (done) => { - try { - interface IState { - timeout: number; - } - const oninitSpy = spy(); - const timeoutSpy = spy(); - const component = { - oninit: ({ state }: Vnode) => new Promise((resolve, reject) => { - oninitSpy(); - setTimeout(() => { - timeoutSpy(); - state.timeout = 50; - resolve(); - }, 50); - }), - timeout: NaN, - view: ({ state }) => state.timeout, - } as Component & IState; - expect(await render(m(component))).to.be.equal(`50`); - expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(timeoutSpy.calledOnce).to.be.equal(true, `oninit was not called`); - expect(timeoutSpy.calledAfter(oninitSpy)).to.be.equal(true, `something really weird happend`); - done(); - } catch (err) { - done(err); - } + it(`oninit`, async () => { + interface IState { + timeout: number; + } + const oninitSpy = spy(); + const timeoutSpy = spy(); + const component = { + oninit: ({ state }: Vnode) => new Promise((resolve, reject) => { + oninitSpy(); + setTimeout(() => { + timeoutSpy(); + state.timeout = 50; + resolve(); + }, 50); + }), + timeout: NaN, + view: ({ state }) => state.timeout, + } as Component & IState; + expect(await render(m(component))).to.be.equal(`50`); + expect(oninitSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(timeoutSpy.calledOnce).to.be.equal(true, `oninit was not called`); + expect(timeoutSpy.calledAfter(oninitSpy)).to.be.equal(true, `something really weird happend`); }); }); @@ -727,10 +481,7 @@ describe(render.name, () => { const cmp = { view: () => m(`#`, `test`), } as Component; - // tslint:disable-next-line:only-arrow-functions - const cmpFactory = function() { - return cmp; - } as FactoryComponent; + const cmpFactory: FactoryComponent = () => cmp; // tslint:disable-next-line:class-name class cmpClass implements ClassComponent { public view() { @@ -738,38 +489,28 @@ describe(render.name, () => { } } - it(`should not fail`, async (done) => { - try { - expect(await render(cmp)).to.be.a(`string`); - expect(await render(cmp)).to.be.equal(`
test
`); - expect(await render(cmpFactory)).to.be.a(`string`); - expect(await render(cmpFactory)).to.be.equal(`
test
`); - expect(await render(cmpClass)).to.be.a(`string`); - expect(await render(cmpClass)).to.be.equal(`
test
`); - done(); - } catch (err) { - done(err); - } - }); - - it(`should have access to attributes`, async (done) => { - try { - const attrSpy = spy(); - const attributes = { - oninit: ({ attrs }) => { - attrSpy(); - expect(attrs.testAttr).to.be.equal(`test`); - }, - testAttr: `test`, - } as Attributes; - expect(await render(cmp, { attrs: attributes })).to.be.a(`string`); - expect(await render(cmpFactory, { attrs: attributes })).to.be.a(`string`); - expect(await render(cmpClass, { attrs: attributes })).to.be.a(`string`); - expect(attrSpy.calledThrice).to.be.equal(true); - done(); - } catch (err) { - done(err); - } + it(`should not fail`, async () => { + expect(await render(cmp)).to.be.a(`string`); + expect(await render(cmp)).to.be.equal(`
test
`); + expect(await render(cmpFactory)).to.be.a(`string`); + expect(await render(cmpFactory)).to.be.equal(`
test
`); + expect(await render(cmpClass)).to.be.a(`string`); + expect(await render(cmpClass)).to.be.equal(`
test
`); + }); + + it(`should have access to attributes`, async () => { + const attrSpy = spy(); + const attributes = { + oninit: ({ attrs }) => { + attrSpy(); + expect(attrs.testAttr).to.be.equal(`test`); + }, + testAttr: `test`, + } as Attributes; + expect(await render(cmp, { attrs: attributes })).to.be.a(`string`); + expect(await render(cmpFactory, { attrs: attributes })).to.be.a(`string`); + expect(await render(cmpClass, { attrs: attributes })).to.be.a(`string`); + expect(attrSpy.calledThrice).to.be.equal(true); }); }); }); @@ -778,10 +519,7 @@ describe(isComponentType.name, () => { const cmp = { view: () => void 0, } as Component; - // tslint:disable-next-line:only-arrow-functions - const cmpFactory = function() { - return cmp; - } as FactoryComponent; + const cmpFactory: FactoryComponent = () => cmp; // tslint:disable-next-line:class-name class cmpClass implements ClassComponent { public view() { diff --git a/index.ts b/index.ts index 46b5941..9d1d906 100644 --- a/index.ts +++ b/index.ts @@ -44,7 +44,7 @@ export function isComponent( export function isClassComponent( component: any, -): component is { new(vnode: CVnode): ClassComponent } { +): component is new (vnode: CVnode) => ClassComponent { return typeof component === "function" && isComponent(component.prototype); } @@ -162,12 +162,12 @@ const parseHooks = async ( } if (typeof onremove === "function") { - hooks.push(async () => await onremove.call(vnode.state, vnode)); + hooks.push(async () => await onremove.call(vnode.state, vnode as any)); } }; export async function render( - view: Children | ComponentTypes, + view: any, { attrs = {}, hooks = [], diff --git a/package.json b/package.json index fe64f2f..701dfc4 100644 --- a/package.json +++ b/package.json @@ -45,20 +45,21 @@ }, "devDependencies": { "@types/chai": "^4.0.0", - "@types/mocha": "^2.2.0", - "@types/sinon": "^2.3.0", + "@types/mocha": "^2.0.0", + "@types/sinon": "^7.0.0", "chai": "^4.0.0", - "coveralls": "^2.13.0", + "coveralls": "^3.0.0", "mithril": "~1.1.0", - "mocha": "^2.2.0", - "nyc": "^11.0.0", - "sinon": "^2.3.0", - "ts-node": "^3.2.0", - "tslint": "^5.5.0", - "typescript": "~2.5.0" + "mocha": "^5.0.0", + "nyc": "^13.0.0", + "sinon": "^7.0.0", + "ts-node": "^8.0.0", + "tslint": "^5.0.0", + "typescript": "^3.0.0", + "typescript-tslint-plugin": "^0.3.0" }, "dependencies": { "@types/mithril": "~1.1.0", - "tslib": "^1.7.0" + "tslib": "^1.9.0" } } diff --git a/tsconfig.json b/tsconfig.json index f28edb4..50455fb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,25 +1,18 @@ { + "compileOnSave": false, "compilerOptions": { + "plugins": [ + { "name": "typescript-tslint-plugin" } + ], "declaration": true, "emitDecoratorMetadata": true, + "esModuleInterop": true, "experimentalDecorators": true, "importHelpers": true, - "inlineSourceMap": false, - "inlineSources": false, - "lib": [ - "dom", - "esnext" - ], - "listFiles": false, + "lib": ["esnext", "dom"], "module": "commonjs", - "noEmitOnError": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "preserveConstEnums": false, - "pretty": true, - "removeComments": true, + "sourceMap": true, "strict": true, - "target": "es6" + "target": "es6", } } diff --git a/types/mithril.d.ts b/types/mithril.d.ts deleted file mode 100644 index d66a9bd..0000000 --- a/types/mithril.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare module "mithril/render/hyperscript"; -declare module "mithril/render/trust";