diff options
author | tg(x) <*@tg-x.net> | 2016-10-05 00:02:10 +0200 |
---|---|---|
committer | tg(x) <*@tg-x.net> | 2016-10-05 00:02:10 +0200 |
commit | ec62d29c90958aa8d41474ed2fe5a179d6fafed7 (patch) | |
tree | 367cc55bc6772cf194ed6c4778cd344d581d3d7c /thirdparty/preact/test/browser/render.js | |
parent | fda241d74d5c1c39203b64da676c684d4dc9d800 (diff) | |
parent | d3ccf4103900b8d990b1970d135695b938d94eae (diff) |
Merge branch 'master' of taler.net:/var/git/wallet-webex
Diffstat (limited to 'thirdparty/preact/test/browser/render.js')
-rw-r--r-- | thirdparty/preact/test/browser/render.js | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/thirdparty/preact/test/browser/render.js b/thirdparty/preact/test/browser/render.js new file mode 100644 index 000000000..5d18fb282 --- /dev/null +++ b/thirdparty/preact/test/browser/render.js @@ -0,0 +1,439 @@ +/* global DISABLE_FLAKEY */ + +import { h, render } from '../../src/preact'; +/** @jsx h */ + +function getAttributes(node) { + let attrs = {}; + for (let i=node.attributes.length; i--; ) { + attrs[node.attributes[i].name] = node.attributes[i].value; + } + return attrs; +} + +// hacky normalization of attribute order across browsers. +function sortAttributes(html) { + return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => { + let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 ); + if (~after.indexOf('/')) after = '></'+pre+'>'; + return '<' + pre + list.join('') + after; + }); +} + +describe('render()', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should create empty nodes (<* />)', () => { + render(<div />, scratch); + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'DIV'); + + scratch.innerHTML = ''; + + render(<span />, scratch); + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'SPAN'); + + scratch.innerHTML = ''; + + render(<foo />, scratch); + render(<x-bar />, scratch); + expect(scratch.childNodes).to.have.length(2); + expect(scratch.childNodes[0]).to.have.property('nodeName', 'FOO'); + expect(scratch.childNodes[1]).to.have.property('nodeName', 'X-BAR'); + }); + + it('should nest empty nodes', () => { + render(( + <div> + <span /> + <foo /> + <x-bar /> + </div> + ), scratch); + + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'DIV'); + + let c = scratch.childNodes[0].childNodes; + expect(c).to.have.length(3); + expect(c).to.have.deep.property('0.nodeName', 'SPAN'); + expect(c).to.have.deep.property('1.nodeName', 'FOO'); + expect(c).to.have.deep.property('2.nodeName', 'X-BAR'); + }); + + it('should not render falsey values', () => { + render(( + <div> + {null},{undefined},{false},{0},{NaN} + </div> + ), scratch); + + expect(scratch.firstChild).to.have.property('innerHTML', ',,,0,NaN'); + }); + + it('should clear falsey attributes', () => { + let root = render(( + <div anull="anull" aundefined="aundefined" afalse="afalse" anan="aNaN" a0="a0" /> + ), scratch); + + root = render(( + <div anull={null} aundefined={undefined} afalse={false} anan={NaN} a0={0} /> + ), scratch, root); + + expect(getAttributes(scratch.firstChild), 'from previous truthy values').to.eql({ + a0: '0', + anan: 'NaN' + }); + + scratch.innerHTML = ''; + + root = render(( + <div anull={null} aundefined={undefined} afalse={false} anan={NaN} a0={0} /> + ), scratch); + + expect(getAttributes(scratch.firstChild), 'initial render').to.eql({ + a0: '0', + anan: 'NaN' + }); + }); + + it('should clear falsey input values', () => { + let root = render(( + <div> + <input value={0} /> + <input value={false} /> + <input value={null} /> + <input value={undefined} /> + </div> + ), scratch); + + expect(root.children[0]).to.have.property('value', '0'); + expect(root.children[1]).to.have.property('value', 'false'); + expect(root.children[2]).to.have.property('value', ''); + expect(root.children[3]).to.have.property('value', ''); + }); + + it('should clear falsey DOM properties', () => { + let root; + function test(val) { + root = render(( + <div> + <input value={val} /> + <table border={val} /> + </div> + ), scratch, root); + } + + test('2'); + test(false); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for false'); + + test('3'); + test(null); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for null'); + + test('4'); + test(undefined); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for undefined'); + }); + + it('should apply string attributes', () => { + render(<div foo="bar" data-foo="databar" />, scratch); + + let div = scratch.childNodes[0]; + expect(div).to.have.deep.property('attributes.length', 2); + + expect(div).to.have.deep.property('attributes[0].name', 'foo'); + expect(div).to.have.deep.property('attributes[0].value', 'bar'); + + expect(div).to.have.deep.property('attributes[1].name', 'data-foo'); + expect(div).to.have.deep.property('attributes[1].value', 'databar'); + }); + + it('should apply class as String', () => { + render(<div class="foo" />, scratch); + expect(scratch.childNodes[0]).to.have.property('className', 'foo'); + }); + + it('should alias className to class', () => { + render(<div className="bar" />, scratch); + expect(scratch.childNodes[0]).to.have.property('className', 'bar'); + }); + + it('should apply style as String', () => { + render(<div style="top:5px; position:relative;" />, scratch); + expect(scratch.childNodes[0]).to.have.deep.property('style.cssText') + .that.matches(/top\s*:\s*5px\s*/) + .and.matches(/position\s*:\s*relative\s*/); + }); + + it('should only register on* functions as handlers', () => { + let click = () => {}, + onclick = () => {}; + + let proto = document.createElement('div').constructor.prototype; + + sinon.spy(proto, 'addEventListener'); + + render(<div click={ click } onClick={ onclick } />, scratch); + + expect(scratch.childNodes[0]).to.have.deep.property('attributes.length', 0); + + expect(proto.addEventListener).to.have.been.calledOnce + .and.to.have.been.calledWithExactly('click', sinon.match.func, false); + + proto.addEventListener.restore(); + }); + + it('should add and remove event handlers', () => { + let click = sinon.spy(), + mousedown = sinon.spy(); + + let proto = document.createElement('div').constructor.prototype; + sinon.spy(proto, 'addEventListener'); + sinon.spy(proto, 'removeEventListener'); + + function fireEvent(on, type) { + let e = document.createEvent('Event'); + e.initEvent(type, true, true); + on.dispatchEvent(e); + } + + render(<div onClick={ () => click(1) } onMouseDown={ mousedown } />, scratch); + + expect(proto.addEventListener).to.have.been.calledTwice + .and.to.have.been.calledWith('click') + .and.calledWith('mousedown'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).to.have.been.calledOnce + .and.calledWith(1); + + proto.addEventListener.reset(); + click.reset(); + + render(<div onClick={ () => click(2) } />, scratch, scratch.firstChild); + + expect(proto.addEventListener).not.to.have.been.called; + + expect(proto.removeEventListener) + .to.have.been.calledOnce + .and.calledWith('mousedown'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).to.have.been.calledOnce + .and.to.have.been.calledWith(2); + + fireEvent(scratch.childNodes[0], 'mousedown'); + expect(mousedown).not.to.have.been.called; + + proto.removeEventListener.reset(); + click.reset(); + mousedown.reset(); + + render(<div />, scratch, scratch.firstChild); + + expect(proto.removeEventListener) + .to.have.been.calledOnce + .and.calledWith('click'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).not.to.have.been.called; + + proto.addEventListener.restore(); + proto.removeEventListener.restore(); + }); + + it('should use capturing for events that do not bubble', () => { + let click = sinon.spy(), + focus = sinon.spy(); + + let root = render(( + <div onClick={click} onFocus={focus}> + <button /> + </div> + ), scratch); + + root.firstElementChild.click(); + root.firstElementChild.focus(); + + expect(click, 'click').to.have.been.calledOnce; + + if (DISABLE_FLAKEY!==true) { + // Focus delegation requires a 50b hack I'm not sure we want to incur + expect(focus, 'focus').to.have.been.calledOnce; + + // IE doesn't set it + expect(click).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing + expect(focus).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing + } + }); + + it('should serialize style objects', () => { + let root = render(( + <div style={{ + color: 'rgb(255, 255, 255)', + background: 'rgb(255, 100, 0)', + backgroundPosition: '10px 10px', + 'background-size': 'cover', + padding: 5, + top: 100, + left: '100%' + }}> + test + </div> + ), scratch); + + let { style } = scratch.childNodes[0]; + expect(style).to.have.property('color').that.equals('rgb(255, 255, 255)'); + expect(style).to.have.property('background').that.contains('rgb(255, 100, 0)'); + expect(style).to.have.property('backgroundPosition').that.equals('10px 10px'); + expect(style).to.have.property('backgroundSize', 'cover'); + expect(style).to.have.property('padding', '5px'); + expect(style).to.have.property('top', '100px'); + expect(style).to.have.property('left', '100%'); + + root = render(( + <div style={{ color: 'rgb(0, 255, 255)' }}>test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('color: rgb(0, 255, 255);'); + + root = render(( + <div style="display: inline;">test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('display: inline;'); + + root = render(( + <div style={{ backgroundColor: 'rgb(0, 255, 255)' }}>test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('background-color: rgb(0, 255, 255);'); + }); + + it('should serialize class/className', () => { + render(<div class={{ + no1: false, + no2: 0, + no3: null, + no4: undefined, + no5: '', + yes1: true, + yes2: 1, + yes3: {}, + yes4: [], + yes5: ' ' + }} />, scratch); + + let { className } = scratch.childNodes[0]; + expect(className).to.be.a.string; + expect(className.split(' ')) + .to.include.members(['yes1', 'yes2', 'yes3', 'yes4', 'yes5']) + .and.not.include.members(['no1', 'no2', 'no3', 'no4', 'no5']); + }); + + it('should support dangerouslySetInnerHTML', () => { + let html = '<b>foo & bar</b>'; + let root = render(<div dangerouslySetInnerHTML={{ __html: html }} />, scratch); + + expect(scratch.firstChild).to.have.property('innerHTML', html); + expect(scratch.innerHTML).to.equal('<div>'+html+'</div>'); + + root = render(<div>a<strong>b</strong></div>, scratch, root); + + expect(scratch).to.have.property('innerHTML', `<div>a<strong>b</strong></div>`); + + root = render(<div dangerouslySetInnerHTML={{ __html: html }} />, scratch, root); + + expect(scratch.innerHTML).to.equal('<div>'+html+'</div>'); + }); + + it('should reconcile mutated DOM attributes', () => { + let check = p => render(<input type="checkbox" checked={p} />, scratch, scratch.lastChild), + value = () => scratch.lastChild.checked, + setValue = p => scratch.lastChild.checked = p; + check(true); + expect(value()).to.equal(true); + check(false); + expect(value()).to.equal(false); + check(true); + expect(value()).to.equal(true); + setValue(true); + check(false); + expect(value()).to.equal(false); + setValue(false); + check(true); + expect(value()).to.equal(true); + }); + + it('should ignore props.children if children are manually specified', () => { + expect( + <div a children={['a', 'b']}>c</div> + ).to.eql( + <div a>c</div> + ); + }); + + it('should reorder child pairs', () => { + let root = render(( + <div> + <a>a</a> + <b>b</b> + </div> + ), scratch, root); + + let a = scratch.firstChild.firstChild; + let b = scratch.firstChild.lastChild; + + expect(a).to.have.property('nodeName', 'A'); + expect(b).to.have.property('nodeName', 'B'); + + root = render(( + <div> + <b>b</b> + <a>a</a> + </div> + ), scratch, root); + + expect(scratch.firstChild.firstChild).to.have.property('nodeName', 'B'); + expect(scratch.firstChild.lastChild).to.have.property('nodeName', 'A'); + expect(scratch.firstChild.firstChild).to.equal(b); + expect(scratch.firstChild.lastChild).to.equal(a); + }); + + // Discussion: https://github.com/developit/preact/issues/287 + ('HTMLDataListElement' in window ? it : xit)('should allow <input list /> to pass through as an attribute', () => { + render(( + <div> + <input type="range" min="0" max="100" list="steplist" /> + <datalist id="steplist"> + <option>0</option> + <option>50</option> + <option>100</option> + </datalist> + </div> + ), scratch); + + let html = scratch.firstElementChild.firstElementChild.outerHTML; + expect(sortAttributes(html)).to.equal(sortAttributes('<input type="range" min="0" max="100" list="steplist">')); + }); +}); |