this.render = sinon.spy((props, state) => <div {...props} >{state.text}</div>);

import { createElement as h, render, Component, Fragment } from '../../src/index';
import { setupScratch, teardown, setupRerender, getMixedArray, mixedArrayHTML } from '../_util/helpers';

/** @jsx h */

let spyAll = obj => Object.keys(obj).forEach( key => sinon.spy(obj,key) );

function getAttributes(node) {
	let attrs = {};
	if (node.attributes) {
		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('Components', () => {

	/** @type {HTMLDivElement} */
	let scratch;

	/** @type {() => void} */
	let rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	describe('Component construction', () => {

		/** @type {object} */
		let instance;
		let PROPS;
		let STATE;

		beforeEach(() => {
			instance = null;
			PROPS = { foo: 'bar', onBaz: () => {} };
			STATE = { text: 'Hello' };
		});

		it('should render components', () => {
			class C1 extends Component {
				render() {
					return
C1
;
				}
			}
			sinon.spy(C1.prototype, 'render');
			render(<C1 />, scratch);

			expect(C1.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch({}, {})
				.and.to.have.returned(sinon.match({ tag: 'div' }));

			expect(scratch.innerHTML).to.equal('
C1
');
		});

		it('should render functional components', () => {
			const C3 = sinon.spy( props =>
 );

			render(<C3 {...PROPS} />, scratch);

			expect(C3)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS)
				.and.to.have.returned(sinon.match({
					tag: 'div',
					props: PROPS
				}));

			expect(scratch.innerHTML).to.equal('

');
		});

		it('should render components with props', () => {
			let constructorProps;

			class C2 extends Component {
				constructor(props) {
					super(props);
					constructorProps = props;
				}
				render(props) {
					return
;
				}
			}
			sinon.spy(C2.prototype, 'render');

			render(<C2 {...PROPS} />, scratch);

			expect(constructorProps).to.deep.equal(PROPS);

			expect(C2.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, {})
				.and.to.have.returned(sinon.match({
					tag: 'div',
					props: PROPS
				}));

			expect(scratch.innerHTML).to.equal('

');
		});

		it('should render Component classes that don\'t pass args into the Component constructor', () => {
			function Foo () {
				Component.call(this);
				instance = this;
				this.state = STATE;
			}
			Foo.prototype.render = sinon.spy((props, state) =>
{state.text}
);

			render(<Foo {...PROPS} />, scratch);

			expect(Foo.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, STATE, {})
				.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));
			expect(instance.props).to.deep.equal(PROPS);
			expect(instance.state).to.deep.equal(STATE);
			expect(instance.context).to.deep.equal({});

			expect(scratch.innerHTML).to.equal('
Hello
');
		});

		it('should render components that don\'t pass args into the Component constructor (unistore pattern)', () => {
			// Pattern unistore uses for connect: https://git.io/fxRqu
			function Wrapper() {
				instance = this;
				this.render = sinon.spy((props, state) =>
{state.text}
);
				this.state = STATE;
			}
			(Wrapper.prototype = new Component()).constructor = Wrapper;

			render(<Wrapper {...PROPS} />, scratch);

			expect(instance.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, STATE, {})
				.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));
			expect(instance.props).to.deep.equal(PROPS);
			expect(instance.state).to.deep.equal({});
			expect(instance.context).to.deep.equal({});

			expect(scratch.innerHTML).to.equal('
Hello
');
		});

		it('should render components that don\'t call Component constructor', () => {
			function Foo() {
				instance = this;
				this.state = STATE;
			}
			Foo.prototype = Object.create(Component);
			Foo.prototype.render = sinon.spy((props, state) =>
{state.text}
);

			render(<Foo {...PROPS} />, scratch);

			expect(Foo.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, STATE, {})
				.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));
			expect(instance.props).to.deep.equal(PROPS);
			expect(instance.state).to.deep.equal(STATE);
			expect(instance.context).to.deep.equal({});

			expect(scratch.innerHTML).to.equal('
Hello
');
		});

		it('should render components that don\'t inherit from Component', () => {
			class Foo {
				constructor() {
					instance = this;
					this.state = STATE;
				}
				render() {
					return
;
				}
			}

			render(<Foo {...PROPS} />, scratch);

			expect(Foo.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, STATE, {})
				.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));
			expect(instance.props).to.deep.equal(PROPS);
			expect(instance.state).to.deep.equal(STATE);
			expect(instance.context).to.deep.equal({});

			expect(scratch.innerHTML).to.equal('
Hello
');
		});

		it('should render components that don\'t inherit from Component (unistore pattern)', () => {
			// Pattern unistore uses for Provider: https://git.io/fxRqR
			function Provider() {
				instance = this;
				this.state = STATE;
			}
			Provider.prototype.render = sinon.spy((props, state) =>
{state.text}
);

			render(<Provider {...PROPS} />, scratch);

			expect(Provider.prototype.render)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS, STATE, {})
				.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));
			expect(instance.props).to.deep.equal(PROPS);
			expect(instance.state).to.deep.equal(STATE);
			expect(instance.context).to.deep.equal({});

			expect(scratch.innerHTML).to.equal('
Hello
');
		});
	});

	it('should render string', () => {
		class StringComponent extends Component {
			render() {
				return 'Hi there';
			}
		}

		render(<StringComponent />, scratch);
		expect(scratch.innerHTML).to.equal('Hi there');
	});

	it('should render number as string', () => {
		class NumberComponent extends Component {
			render() {
				return 42;
			}
		}

		render(<NumberComponent />, scratch);
		expect(scratch.innerHTML).to.equal('42');
	});

	it('should render null as empty string', () => {
		class NullComponent extends Component {
			render() {
				return null;
			}
		}

		render(<NullComponent />, scratch);
		expect(scratch.innerHTML).to.equal('');
	});

	// Test for Issue #73
	it('should remove orphaned elements replaced by Components', () => {
		class Comp extends Component {
			render() {
				return span in a component;
			}
		}

		let root;
		function test(content) {
			root = render(content, scratch, root);
		}

		test(<Comp />);
		test(
just a div
);
		test(<Comp />);

		expect(scratch.innerHTML).to.equal('span in a component');
	});

	// Test for Issue developit/preact#176
	it('should remove children when root changes to text node', () => {
		let comp;

		class Comp extends Component {
			constructor() {
				super();
				comp = this;
			}
			render(_, { alt }) {
				return alt ? 'asdf' :
test
;
			}
		}

		render(<Comp />, scratch);

		comp.setState({ alt: true });
		comp.forceUpdate();
		expect(scratch.innerHTML, 'switching to textnode').to.equal('asdf');

		comp.setState({ alt: false });
		comp.forceUpdate();
		expect(scratch.innerHTML, 'switching to element').to.equal('
test
');

		comp.setState({ alt: true });
		comp.forceUpdate();
		expect(scratch.innerHTML, 'switching to textnode 2').to.equal('asdf');
	});

	// Test for Issue developit/preact#254
	it('should not recycle common class children with different keys', () => {
		let idx = 0;
		let msgs = ['A','B','C','D','E','F','G','H'];
		let sideEffect = sinon.spy();

		class Comp extends Component {
			componentWillMount() {
				this.innerMsg = msgs[(idx++ % 8)];
				sideEffect();
			}
			render() {
				return
{this.innerMsg}
;
			}
		}
		sinon.spy(Comp.prototype, 'componentWillMount');

		let good, bad;
		class GoodContainer extends Component {
			constructor(props) {
				super(props);
				this.state.alt = false;
				good = this;
			}

			render(_, { alt }) {
				return (

						{alt ? null : (<Comp key={1} alt={alt} />)}
						{alt ? null : (<Comp key={2} alt={alt} />)}
						{alt ? (<Comp key={3} alt={alt} />) : null}

				);
			}
		}

		class BadContainer extends Component {
			constructor(props) {
				super(props);
				this.state.alt = false;
				bad = this;
			}

			render(_, { alt }) {
				return (

						{alt ? null : (<Comp alt={alt} />)}
						{alt ? null : (<Comp alt={alt} />)}
						{alt ? (<Comp alt={alt} />) : null}

				);
			}
		}

		render(<GoodContainer />, scratch);
		expect(scratch.textContent, 'new component with key present').to.equal('AB');
		expect(Comp.prototype.componentWillMount).to.have.been.calledTwice;
		expect(sideEffect).to.have.been.calledTwice;

		sideEffect.resetHistory();
		Comp.prototype.componentWillMount.resetHistory();
		good.setState({ alt: true });
		rerender();
		expect(scratch.textContent, 'new component with key present re-rendered').to.equal('C');
		//we are recycling the first 2 components already rendered, just need a new one
		expect(Comp.prototype.componentWillMount).to.have.been.calledOnce;
		expect(sideEffect).to.have.been.calledOnce;

		sideEffect.resetHistory();
		Comp.prototype.componentWillMount.resetHistory();
		render(<BadContainer />, scratch);
		expect(scratch.textContent, 'new component without key').to.equal('DE');
		expect(Comp.prototype.componentWillMount).to.have.been.calledTwice;
		expect(sideEffect).to.have.been.calledTwice;

		sideEffect.resetHistory();
		Comp.prototype.componentWillMount.resetHistory();
		bad.setState({ alt: true });
		rerender();
		expect(scratch.textContent, 'new component without key re-rendered').to.equal('D');
		expect(Comp.prototype.componentWillMount).to.not.have.been.called;
		expect(sideEffect).to.not.have.been.called;
	});

	describe('array children', () => {
		it('should render DOM element\'s array children', () => {
			render(
{getMixedArray()}
, scratch);
			expect(scratch.firstChild.innerHTML).to.equal(mixedArrayHTML);
		});

		it('should render Component\'s array children', () => {
			const Foo = () => getMixedArray();

			render(<Foo />, scratch);

			expect(scratch.innerHTML).to.equal(mixedArrayHTML);
		});

		it('should render Fragment\'s array children', () => {
			const Foo = () => (
				<Fragment>
					{getMixedArray()}
				</Fragment>
			);

			render(<Foo />, scratch);

			expect(scratch.innerHTML).to.equal(mixedArrayHTML);
		});

		it('should render sibling array children', () => {
			const Todo = () => (

A header

					{['a','b'].map(value =>
{value}
)}

A divider

					{['c', 'd'].map(value =>
{value}
)}

A footer

			);

			render(<Todo />, scratch);

			let ul = scratch.firstChild;
			expect(ul.childNodes.length).to.equal(7);
			expect(ul.childNodes[0].textContent).to.equal('A header');
			expect(ul.childNodes[1].textContent).to.equal('a');
			expect(ul.childNodes[2].textContent).to.equal('b');
			expect(ul.childNodes[3].textContent).to.equal('A divider');
			expect(ul.childNodes[4].textContent).to.equal('c');
			expect(ul.childNodes[5].textContent).to.equal('d');
			expect(ul.childNodes[6].textContent).to.equal('A footer');
		});
	});

	describe('props.children', () => {
		it('should support passing children as a prop', () => {
			const Foo = props =>
;

			render(<Foo a="b" children={[
				bar,
				'123',
				456
			]}
			       />, scratch);

			expect(scratch.innerHTML).to.equal('
bar123456
');
		});

		it('should be ignored when explicit children exist', () => {
			const Foo = props =>
a
;

			render(<Foo children={'b'} />, scratch);

			expect(scratch.innerHTML).to.equal('
a
');
		});

		// TODO: Not currently supported. Redefine these tests once we settle
		// on children normalization
		xdescribe('should always be an array', () => {
			let children;

			let Foo = props => {
				children = props.children;
				return
{props.children}
;
			};

			let FunctionFoo = props => {
				children = props.children;
				return
{props.children[0](2)}
;
			};

			let Bar = () => Bar;

			beforeEach(() => {
				children = undefined;
			});

			it('with no child', () => {
				render(<Foo />, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(0);
				expect(scratch.innerHTML).to.equal('

');
			});

			it('with a text child', () => {
				render(<Foo>text</Foo>, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(1);
				expect(children[0]).to.be.a('string');
				expect(scratch.innerHTML).to.equal('
text
');
			});

			it('with a DOM node child', () => {
				render(<Foo></Foo>, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(1);
				expect(children[0]).to.be.an('object');
				expect(scratch.innerHTML).to.equal('

');
			});

			it('with a Component child', () => {
				render(<Foo><Bar /></Foo>, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(1);
				expect(children[0]).to.be.an('object');
				expect(scratch.innerHTML).to.equal('
Bar
');
			});

			it('with a function child', () => {
				render(<FunctionFoo>{num => num.toFixed(2)}</FunctionFoo>, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(1);
				expect(children[0]).to.be.a('function');
				expect(scratch.innerHTML).to.equal('
2.00
');
			});

			it('with multiple children', () => {
				render(<Foo><Bar /></Foo>, scratch);

				expect(children).to.be.an('array');
				expect(children).to.have.lengthOf(3);
				expect(children[0]).to.be.an('object');
				expect(scratch.innerHTML).to.equal('
Bar
');
			});
		});
	});

	describe('High-Order Components', () => {
		it('should render wrapper HOCs', () => {
			const text = 'We\'ll throw some happy little limbs on this tree.';

			function withBobRoss(ChildComponent) {
				return class BobRossIpsum extends Component {
					getChildContext() {
						return { text };
					}

					render(props) {
						return <ChildComponent {...props} />;
					}
				};
			}

			const PaintSomething = (props, context) =>
{context.text}
;
			const Paint = withBobRoss(PaintSomething);

			render(<Paint />, scratch);
			expect(scratch.innerHTML).to.equal(`<div>${text}</div>`);
		});

		it('should render HOCs with generic children', () => {
			const text = 'Let your imagination just wonder around when you\'re doing these things.';

			class BobRossProvider extends Component {
				getChildContext() {
					return { text };
				}

				render(props) {
					return props.children;
				}
			}

			function BobRossConsumer(props, context) {
				return props.children(context.text);
			}

			const Say = props =>
{props.text}
;

			const Speak = () => (
				<BobRossProvider>
					A span
					<BobRossConsumer>
						{ text => <Say text={text} /> }
					</BobRossConsumer>
					A final span
				</BobRossProvider>
			);

			render(<Speak />, scratch);

			expect(scratch.innerHTML).to.equal(`<span>A span</span><div>${text}</div><span>A final span</span>`);
		});

		it('should render nested functional components', () => {
			const PROPS = { foo: 'bar', onBaz: () => {} };

			const Outer = sinon.spy(
				props => <Inner {...props} />
			);

			const Inner = sinon.spy(
				props =>
inner

			);

			render(<Outer {...PROPS} />, scratch);

			expect(Outer)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS)
				.and.to.have.returned(sinon.match({
					tag: Inner,
					props: PROPS
				}));

			expect(Inner)
				.to.have.been.calledOnce
				.and.to.have.been.calledWithMatch(PROPS)
				.and.to.have.returned(sinon.match({
					tag: 'div',
					props: { ...PROPS, children: 'inner' }
				}));

			expect(scratch.innerHTML).to.equal('
inner
');
		});

		it('should re-render nested functional components', () => {
			let doRender = null;
			class Outer extends Component {
				componentDidMount() {
					let i = 1;
					doRender = () => this.setState({ i: ++i });
				}
				componentWillUnmount() {}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			sinon.spy(Outer.prototype, 'render');
			sinon.spy(Outer.prototype, 'componentWillUnmount');

			let j = 0;
			const Inner = sinon.spy(
				props =>
inner

			);

			render(<Outer foo="bar" />, scratch);

			// update & flush
			doRender();
			rerender();

			expect(Outer.prototype.componentWillUnmount)
				.not.to.have.been.called;

			expect(Inner).to.have.been.calledTwice;

			expect(Inner.secondCall)
				.to.have.been.calledWithMatch({ foo: 'bar', i: 2 })
				.and.to.have.returned(sinon.match({
					props: {
						j: 2,
						i: 2,
						foo: 'bar'
					}
				}));

			expect(getAttributes(scratch.firstElementChild)).to.eql({
				j: '2',
				i: '2',
				foo: 'bar'
			});

			// update & flush
			doRender();
			rerender();

			expect(Inner).to.have.been.calledThrice;

			expect(Inner.thirdCall)
				.to.have.been.calledWithMatch({ foo: 'bar', i: 3 })
				.and.to.have.returned(sinon.match({
					props: {
						j: 3,
						i: 3,
						foo: 'bar'
					}
				}));

			expect(getAttributes(scratch.firstElementChild)).to.eql({
				j: '3',
				i: '3',
				foo: 'bar'
			});
		});

		it('should re-render nested components', () => {
			let doRender = null,
				alt = false;

			class Outer extends Component {
				componentDidMount() {
					let i = 1;
					doRender = () => this.setState({ i: ++i });
				}
				componentWillUnmount() {}
				render(props, { i }) {
					if (alt) return
;
					return <Inner i={i} {...props} />;
				}
			}
			sinon.spy(Outer.prototype, 'render');
			sinon.spy(Outer.prototype, 'componentDidMount');
			sinon.spy(Outer.prototype, 'componentWillUnmount');

			let j = 0;
			class Inner extends Component {
				constructor(...args) {
					super();
					this._constructor(...args);
				}
				_constructor() {}
				componentWillMount() {}
				componentDidMount() {}
				componentWillUnmount() {}
				render(props) {
					return
inner
;
				}
			}
			sinon.spy(Inner.prototype, '_constructor');
			sinon.spy(Inner.prototype, 'render');
			sinon.spy(Inner.prototype, 'componentWillMount');
			sinon.spy(Inner.prototype, 'componentDidMount');
			sinon.spy(Inner.prototype, 'componentWillUnmount');

			render(<Outer foo="bar" />, scratch);

			expect(Outer.prototype.componentDidMount).to.have.been.calledOnce;

			// update & flush
			doRender();
			rerender();

			expect(Outer.prototype.componentWillUnmount).not.to.have.been.called;

			expect(Inner.prototype._constructor).to.have.been.calledOnce;
			expect(Inner.prototype.componentWillUnmount).not.to.have.been.called;
			expect(Inner.prototype.componentWillMount).to.have.been.calledOnce;
			expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
			expect(Inner.prototype.render).to.have.been.calledTwice;

			expect(Inner.prototype.render.secondCall)
				.to.have.been.calledWithMatch({ foo: 'bar', i: 2 })
				.and.to.have.returned(sinon.match({
					props: {
						j: 2,
						i: 2,
						foo: 'bar'
					}
				}));

			expect(getAttributes(scratch.firstElementChild)).to.eql({
				j: '2',
				i: '2',
				foo: 'bar'
			});

			expect(sortAttributes(scratch.innerHTML)).to.equal(sortAttributes('
inner
'));

			// update & flush
			doRender();
			rerender();

			expect(Inner.prototype.componentWillUnmount).not.to.have.been.called;
			expect(Inner.prototype.componentWillMount).to.have.been.calledOnce;
			expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
			expect(Inner.prototype.render).to.have.been.calledThrice;

			expect(Inner.prototype.render.thirdCall)
				.to.have.been.calledWithMatch({ foo: 'bar', i: 3 })
				.and.to.have.returned(sinon.match({
					props: {
						j: 3,
						i: 3,
						foo: 'bar'
					}
				}));

			expect(getAttributes(scratch.firstElementChild)).to.eql({
				j: '3',
				i: '3',
				foo: 'bar'
			});

			// update & flush
			alt = true;
			doRender();
			rerender();

			expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce;

			expect(scratch.innerHTML).to.equal('

');

			// update & flush
			alt = false;
			doRender();
			rerender();

			expect(sortAttributes(scratch.innerHTML)).to.equal(sortAttributes('
inner
'));
		});

		it('should resolve intermediary functional component', () => {
			let ctx = {};
			class Root extends Component {
				getChildContext() {
					return { ctx };
				}
				render() {
					return <Func />;
				}
			}
			const Func = () => <Inner />;
			class Inner extends Component {
				componentWillMount() {}
				componentDidMount() {}
				componentWillUnmount() {}
				render() {
					return
inner
;
				}
			}

			spyAll(Inner.prototype);

			render(<Root />, scratch);

			expect(Inner.prototype.componentWillMount).to.have.been.calledOnce;
			expect(Inner.prototype.componentDidMount).to.have.been.calledOnce;
			expect(Inner.prototype.componentWillMount).to.have.been.calledBefore(Inner.prototype.componentDidMount);

			render(<asdf />, scratch);

			expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce;
		});

		it('should unmount children of high-order components without unmounting parent', () => {
			let outer, inner2, counter=0;

			class Outer extends Component {
				constructor(props, context) {
					super(props, context);
					outer = this;
					this.state = {
						child: this.props.child
					};
				}
				componentWillUnmount(){}
				componentWillMount(){}
				componentDidMount(){}
				render(_, { child: C }) {
					return <C />;
				}
			}
			spyAll(Outer.prototype);

			class Inner extends Component {
				componentWillUnmount(){}
				componentWillMount(){}
				componentDidMount(){}
				render() {
					return h('element'+(++counter));
				}
			}
			spyAll(Inner.prototype);

			class Inner2 extends Component {
				constructor(props, context) {
					super(props, context);
					inner2 = this;
				}
				componentWillUnmount(){}
				componentWillMount(){}
				componentDidMount(){}
				render() {
					return h('element'+(++counter));
				}
			}
			spyAll(Inner2.prototype);

			render(<Outer child={Inner} />, scratch);

			// outer should only have been mounted once
			expect(Outer.prototype.componentWillMount, 'outer initial').to.have.been.calledOnce;
			expect(Outer.prototype.componentDidMount, 'outer initial').to.have.been.calledOnce;
			expect(Outer.prototype.componentWillUnmount, 'outer initial').not.to.have.been.called;

			// inner should only have been mounted once
			expect(Inner.prototype.componentWillMount, 'inner initial').to.have.been.calledOnce;
			expect(Inner.prototype.componentDidMount, 'inner initial').to.have.been.calledOnce;
			expect(Inner.prototype.componentWillUnmount, 'inner initial').not.to.have.been.called;

			outer.setState({ child: Inner2 });
			outer.forceUpdate();

			expect(Inner2.prototype.render).to.have.been.calledOnce;

			// outer should still only have been mounted once
			expect(Outer.prototype.componentWillMount, 'outer swap').to.have.been.calledOnce;
			expect(Outer.prototype.componentDidMount, 'outer swap').to.have.been.calledOnce;
			expect(Outer.prototype.componentWillUnmount, 'outer swap').not.to.have.been.called;

			// inner should only have been mounted once
			expect(Inner2.prototype.componentWillMount, 'inner2 swap').to.have.been.calledOnce;
			expect(Inner2.prototype.componentDidMount, 'inner2 swap').to.have.been.calledOnce;
			expect(Inner2.prototype.componentWillUnmount, 'inner2 swap').not.to.have.been.called;

			inner2.forceUpdate();

			expect(Inner2.prototype.render, 'inner2 update').to.have.been.calledTwice;
			expect(Inner2.prototype.componentWillMount, 'inner2 update').to.have.been.calledOnce;
			expect(Inner2.prototype.componentDidMount, 'inner2 update').to.have.been.calledOnce;
			expect(Inner2.prototype.componentWillUnmount, 'inner2 update').not.to.have.been.called;
		});

		it('should remount when swapping between HOC child types', () => {
			class Outer extends Component {
				render({ child: Child }) {
					return <Child />;
				}
			}

			class Inner extends Component {
				componentWillMount() {}
				componentWillUnmount() {}
				render() {
					return
foo
;
				}
			}
			spyAll(Inner.prototype);

			const InnerFunc = () => (

bar

			);

			render(<Outer child={Inner} />, scratch);

			expect(Inner.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce;
			expect(Inner.prototype.componentWillUnmount, 'initial mount').not.to.have.been.called;

			Inner.prototype.componentWillMount.resetHistory();
			render(<Outer child={InnerFunc} />, scratch);

			expect(Inner.prototype.componentWillMount, 'unmount').not.to.have.been.called;
			expect(Inner.prototype.componentWillUnmount, 'unmount').to.have.been.calledOnce;

			Inner.prototype.componentWillUnmount.resetHistory();
			render(<Outer child={Inner} />, scratch);

			expect(Inner.prototype.componentWillMount, 'remount').to.have.been.calledOnce;
			expect(Inner.prototype.componentWillUnmount, 'remount').not.to.have.been.called;
		});
	});

	describe('Component Nesting', () => {
		let useIntermediary = false;

		let createComponent = (Intermediary) => {
			class C extends Component {
				componentWillMount() {}
				render({ children }) {
					if (!useIntermediary) return children;
					let I = useIntermediary===true ? Intermediary : useIntermediary;
					return {children};
				}
			}
			spyAll(C.prototype);
			return C;
		};

		let createFunction = () => sinon.spy( ({ children }) => children );

		let F1 = createFunction();
		let F2 = createFunction();
		let F3 = createFunction();

		let C1 = createComponent(F1);
		let C2 = createComponent(F2);
		let C3 = createComponent(F3);

		let reset = () => [C1, C2, C3].reduce(
			(acc, c) => acc.concat( Object.keys(c.prototype).map(key => c.prototype[key]) ),
			[F1, F2, F3]
		).forEach( c => c.resetHistory() );

		it('should handle lifecycle for no intermediary in component tree', () => {
			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce;
			expect(C2.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce;

			reset();
			render(<C1><C2>Some Text</C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'unmount innermost, C1').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'unmount innermost, C2').not.to.have.been.called;

			reset();
			render(<C1><C3>Some Text</C3></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'swap innermost').not.to.have.been.called;
			expect(C3.prototype.componentWillMount, 'swap innermost').to.have.been.calledOnce;

			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'inject between, C1').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'inject between, C2').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'inject between, C3').to.have.been.calledOnce;
		});

		it('should handle lifecycle for nested intermediary functional components', () => {
			useIntermediary = true;

			render(
, scratch);
			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'initial mount w/ intermediary fn, C1').to.have.been.calledOnce;
			expect(C2.prototype.componentWillMount, 'initial mount w/ intermediary fn, C2').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'initial mount w/ intermediary fn, C3').to.have.been.calledOnce;

			reset();
			render(<C1><C2>Some Text</C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'unmount innermost w/ intermediary fn, C1').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary fn, C2').not.to.have.been.called;

			reset();
			render(<C1><C3>Some Text</C3></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'swap innermost w/ intermediary fn').not.to.have.been.called;
			expect(C3.prototype.componentWillMount, 'swap innermost w/ intermediary fn').to.have.been.calledOnce;

			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'inject between, C1 w/ intermediary fn').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'inject between, C2 w/ intermediary fn').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'inject between, C3 w/ intermediary fn').to.have.been.calledOnce;
		});

		it('should handle lifecycle for nested intermediary elements', () => {
			useIntermediary = 'div';

			render(
, scratch);
			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'initial mount w/ intermediary div, C1').to.have.been.calledOnce;
			expect(C2.prototype.componentWillMount, 'initial mount w/ intermediary div, C2').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'initial mount w/ intermediary div, C3').to.have.been.calledOnce;

			reset();
			render(<C1><C2>Some Text</C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C1').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C2').not.to.have.been.called;

			reset();
			render(<C1><C3>Some Text</C3></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'swap innermost w/ intermediary div').not.to.have.been.called;
			expect(C3.prototype.componentWillMount, 'swap innermost w/ intermediary div').to.have.been.calledOnce;

			reset();
			render(<C1><C2><C3>Some Text</C3></C2></C1>, scratch);

			expect(C1.prototype.componentWillMount, 'inject between, C1 w/ intermediary div').not.to.have.been.called;
			expect(C2.prototype.componentWillMount, 'inject between, C2 w/ intermediary div').to.have.been.calledOnce;
			expect(C3.prototype.componentWillMount, 'inject between, C3 w/ intermediary div').to.have.been.calledOnce;
		});
	});
});


/* global DISABLE_FLAKEY */

import { createElement as h, render, Component } from '../../src/index';
import { setupScratch, setupRerender, teardown, getMixedArray, mixedArrayHTML } from '../_util/helpers';

/** @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, rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should render nothing node given null', () => {
		render(null, scratch);
		let c = scratch.childNodes;
		expect(c).to.have.length(0);
	});

	it('should render an empty text node given an empty string', () => {
		render('', scratch);
		let c = scratch.childNodes;
		expect(c).to.have.length(1);
		expect(c[0].data).to.equal('');
		expect(c[0].nodeName).to.equal('#text');
	});

	it('should allow node type change with content', () => {
		render(Bad, scratch);
		render(
Good
, scratch);
		expect(scratch.innerHTML).to.eql(
			`<div>Good</div>`
		);
	});

	it('should create empty nodes (<* />)', () => {
		render(
, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.childNodes[0].nodeName).to.equal('DIV');

		scratch.parentNode.removeChild(scratch);
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);

		render(, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.childNodes[0].nodeName).to.equal('SPAN');

	});

	it('should support custom tag names', () => {
		render(<foo />, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.firstChild).to.have.property('nodeName', 'FOO');

		scratch.parentNode.removeChild(scratch);
		scratch = document.createElement('div');
		(document.body || document.documentElement).appendChild(scratch);

		render(<x-bar />, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.firstChild).to.have.property('nodeName', 'X-BAR');
	});

	it('should allow element reuse', () => {
		let reused =
Hello World!
;
		render(
{reused}{reused}/>
, scratch);
		render(
{reused}
, scratch);
		expect(scratch.innerHTML).to.eql(
			`<div><span></span><div>Hello World!</div></div>`
		);
	});

	// Not supported currently
	xit('should append new elements when called without a merge argument', () => {
		render(
, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.firstChild).to.have.property('nodeName', 'DIV');

		render(, scratch);
		expect(scratch.childNodes).to.have.length(2);
		expect(scratch.childNodes[0]).to.have.property('nodeName', 'DIV');
		expect(scratch.childNodes[1]).to.have.property('nodeName', 'SPAN');
	});

	it('should merge new elements when called twice', () => {
		render(
, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.firstChild).to.have.property('nodeName', 'DIV');

		render(, scratch);
		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.firstChild).to.have.property('nodeName', 'SPAN');
	});

	it('should nest empty nodes', () => {
		render((

				<foo />
				<x-bar />

		), scratch);

		expect(scratch.childNodes).to.have.length(1);
		expect(scratch.childNodes[0].nodeName).to.equal('DIV');

		let c = scratch.childNodes[0].childNodes;
		expect(c).to.have.length(3);
		expect(c[0].nodeName).to.equal('SPAN');
		expect(c[1].nodeName).to.equal('FOO');
		expect(c[2].nodeName).to.equal('X-BAR');
	});

	it('should not render falsy values', () => {
		render((

				{null},{undefined},{false},{0},{NaN}

		), scratch);

		expect(scratch.firstChild).to.have.property('innerHTML', ',,,0,NaN');
	});

	it('should not render null', () => {
		render(null, scratch);
		expect(scratch.innerHTML).to.equal('');
	});

	it('should not render undefined', () => {
		render(undefined, scratch);
		expect(scratch.innerHTML).to.equal('');
	});

	it('should not render boolean true', () => {
		render(true, scratch);
		expect(scratch.innerHTML).to.equal('');
	});

	it('should not render boolean false', () => {
		render(false, scratch);
		expect(scratch.innerHTML).to.equal('');
	});

	it('should render NaN as text content', () => {
		render(NaN, scratch);
		expect(scratch.innerHTML).to.equal('NaN');
	});

	it('should render numbers (0) as text content', () => {
		render(0, scratch);
		expect(scratch.innerHTML).to.equal('0');
	});

	it('should render numbers (42) as text content', () => {
		render(42, scratch);
		expect(scratch.innerHTML).to.equal('42');
	});

	it('should render strings as text content', () => {
		render('Testing, huh! How is it going?', scratch);
		expect(scratch.innerHTML).to.equal('Testing, huh! How is it going?');
	});

	it('should render arrays of mixed elements', () => {
		render(getMixedArray(), scratch);
		expect(scratch.innerHTML).to.equal(mixedArrayHTML);
	});

	it('should clear falsy attributes', () => {
		render((

		), scratch);

		render((

		), scratch);

		expect(getAttributes(scratch.firstChild), 'from previous truthy values').to.eql({
			a0: '0',
			anan: 'NaN'
		});

	});

	it('should not render falsy attributes on hydrate', () => {
		render((

		), scratch);

		expect(getAttributes(scratch.firstChild), 'initial render').to.eql({
			a0: '0',
			anan: 'NaN'
		});
	});

	it('should clear falsy input values', () => {
		// Note: this test just demonstrates the default browser behavior
		render((

				<input value={0} />
				<input value={false} />
				<input value={null} />
				<input value={undefined} />

		), scratch);

		let root = scratch.firstChild;
		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 not clear falsy DOM properties', () => {
		function test(val) {
			render((

					<input value={val} />

			), scratch);
		}

		test('2');
		test(false);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for false');

		test('3');
		test(null);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for null');

		test('4');
		test(undefined);
		expect(scratch).to.have.property('innerHTML', '
<input>

', 'for undefined');
	});

	// Test for developit/preact#651
	it('should set enumerable boolean attribute', () => {
		render(<input spellcheck={false} />, scratch);
		expect(scratch.firstChild.spellcheck).to.equal(false);
	});

	it('should apply string attributes', () => {
		render(
, scratch);

		let div = scratch.childNodes[0];
		expect(div.attributes.length).to.equal(2);

		expect(div.attributes[0].name).to.equal('foo');
		expect(div.attributes[0].value).to.equal('bar');

		expect(div.attributes[1].name).to.equal('data-foo');
		expect(div.attributes[1].value).to.equal('databar');
	});

	it('should not serialize function props as attributes', () => {
		render(
, scratch);

		let div = scratch.childNodes[0];
		expect(div.attributes.length).to.equal(0);
	});

	it('should serialize object props as attributes', () => {
		render(
, scratch);

		let div = scratch.childNodes[0];
		expect(div.attributes.length).to.equal(2);

		expect(div.attributes[0].name).equal('foo');
		expect(div.attributes[0].value).equal('[object Object]');

		expect(div.attributes[1].name).equal('bar');
		expect(div.attributes[1].value).equal('abc');
	});

	it('should apply class as String', () => {
		render(
, scratch);
		expect(scratch.childNodes[0]).to.have.property('className', 'foo');
	});

	it('should alias className to class', () => {
		render(
, scratch);
		expect(scratch.childNodes[0]).to.have.property('className', 'bar');
	});

	describe('style attribute', () => {
		it('should apply style as String', () => {
			render(
, scratch);
			expect(scratch.childNodes[0].style.cssText)
				.that.matches(/top\s*:\s*5px\s*/)
				.and.matches(/position\s*:\s*relative\s*/);
		});

		it('should properly switch from string styles to object styles and back', () => {
			render((

test

			), scratch);

			let root = scratch.firstChild;
			expect(root.style.cssText).to.equal('display: inline;');

			render((

			), scratch);

			expect(root.style.cssText).to.equal('color: red;');

			render((

			), scratch);

			expect(root.style.cssText).to.equal('color: blue;');

			render((

			), scratch);

			expect(root.style.cssText).to.equal('color: yellow;');

			render((

			), scratch);

			expect(root.style.cssText).to.equal('display: block;');
		});

		it('should serialize style objects', () => {
			render((

					test

			), scratch);

			let root = scratch.firstChild;
			let { style } = root;
			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%');

			render((

test

			), scratch);

			expect(root.style.cssText).to.equal('color: rgb(0, 255, 255);');

			render((

test

			), scratch);

			expect(root.style.cssText).to.equal('background-color: rgb(0, 255, 255);');
		});

	});

	describe('event handling', () => {
		let proto;

		function fireEvent(on, type) {
			let e = document.createEvent('Event');
			e.initEvent(type, true, true);
			on.dispatchEvent(e);
		}

		beforeEach(() => {
			proto = document.createElement('div').constructor.prototype;

			sinon.spy(proto, 'addEventListener');
			sinon.spy(proto, 'removeEventListener');
		});

		afterEach(() => {
			proto.addEventListener.restore();
			proto.removeEventListener.restore();
		});

		it('should only register on* functions as handlers', () => {
			let click = () => {},
				onclick = () => {};

			render(
, scratch);

			expect(scratch.childNodes[0].attributes.length).to.equal(0);

			expect(proto.addEventListener).to.have.been.calledOnce
				.and.to.have.been.calledWithExactly('click', sinon.match.func, false);
		});

		it('should support native event names', () => {
			let click = sinon.spy(),
				mousedown = sinon.spy();

			render(
 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);
		});

		it('should support camel-case event names', () => {
			let click = sinon.spy(),
				mousedown = sinon.spy();

			render(
 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);
		});

		it('should update event handlers', () => {
			let click1 = sinon.spy();
			let click2 = sinon.spy();

			render(
, scratch);

			fireEvent(scratch.childNodes[0], 'click');
			expect(click1).to.have.been.calledOnce;
			expect(click2).to.not.have.been.called;

			click1.resetHistory();
			click2.resetHistory();

			render(
, scratch);

			fireEvent(scratch.childNodes[0], 'click');
			expect(click1).to.not.have.been.called;
			expect(click2).to.have.been.called;
		});

		it('should remove event handlers', () => {
			let click = sinon.spy(),
				mousedown = sinon.spy();

			render(
 click(1)} onMouseDown={mousedown} />, scratch);
			render(
 click(2)} />, scratch);

			expect(proto.removeEventListener)
				.to.have.been.calledWith('mousedown');

			fireEvent(scratch.childNodes[0], 'mousedown');
			expect(mousedown).not.to.have.been.called;

			proto.removeEventListener.resetHistory();
			click.resetHistory();
			mousedown.resetHistory();

			render(
, scratch);

			expect(proto.removeEventListener)
				.to.have.been.calledWith('click');

			fireEvent(scratch.childNodes[0], 'click');
			expect(click).not.to.have.been.called;
		});

		it('should use capturing for event props ending with *Capture', () => {
			let click = sinon.spy(),
				focus = sinon.spy();

			render((

					<button />

			), scratch);

			let root = scratch.firstChild;
			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
			}
		});
	});

	describe('dangerouslySetInnerHTML', () => {
		it('should support dangerouslySetInnerHTML', () => {
			let html = 'foo & bar';
			// eslint-disable-next-line react/no-danger
			render(
, scratch);

			expect(scratch.firstChild, 'set').to.have.property('innerHTML', html);
			expect(scratch.innerHTML).to.equal('
'+html+'
');

			render(
ab
, scratch);

			expect(scratch, 'unset').to.have.property('innerHTML', `<div>a<strong>b</strong></div>`);

			// eslint-disable-next-line react/no-danger
			render(
, scratch);
			expect(scratch.innerHTML, 're-set').to.equal('
'+html+'
');
		});

		it('should apply proper mutation for VNodes with dangerouslySetInnerHTML attr', () => {
			let thing;
			class Thing extends Component {
				constructor(props, context) {
					super(props, context);
					this.state.html = this.props.html;
					thing = this;
				}
				render(props, { html }) {
					// eslint-disable-next-line react/no-danger
					return html ?
 :
;
				}
			}

			render(<Thing html="test" />, scratch);
			expect(scratch.innerHTML).to.equal('
test
');

			thing.setState({ html: false });
			rerender();
			expect(scratch.innerHTML).to.equal('

');

			thing.setState({ html: '<foo><bar>test</bar></foo>' });
			rerender();
			expect(scratch.innerHTML).to.equal('
<foo><bar>test</bar></foo>
');
		});

		it('should hydrate with dangerouslySetInnerHTML', () => {
			let html = 'foo & bar';
			scratch.innerHTML = `<div>${html}</div>`;
			// eslint-disable-next-line react/no-danger
			render(
, scratch);

			expect(scratch.firstChild).to.have.property('innerHTML', html);
			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(

c

		).to.eql(

c

		);
	});

	it('should reorder child pairs', () => {
		let root = render((

				a
				b

		), 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((

				b
				a

		), 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);
	});

	it('should not merge attributes with node created by the DOM', () => {
		const html = (htmlString) => {
			const div = document.createElement('div');
			div.innerHTML = htmlString;
			return div.firstChild;
		};

		const DOMElement = html`<div><a foo="bar"></a></div>`;
		const preactElement =

;

		render(preactElement, scratch, DOMElement);
		expect(scratch).to.have.property('innerHTML', '

');
	});

	it.skip('should skip non-preact elements', () => {
		let comp;
		class Foo extends Component {
			constructor() {
				super();
				comp = this;
			}

			render() {
				let alt = this.props.alt || this.state.alt || this.alt;
				let c = [
					foo,
					{ alt?'alt':'bar' }
				];
				if (alt) c.reverse();
				return
{c}
;
			}
		}

		render(<Foo />, scratch);

		let c = document.createElement('c');
		c.textContent = 'baz';
		comp.base.appendChild(c);

		let b = document.createElement('b');
		b.textContent = 'bat';
		comp.base.appendChild(b);

		expect(scratch.firstChild.children, 'append').to.have.length(4);

		comp.forceUpdate();

		expect(scratch.firstChild.children, 'forceUpdate').to.have.length(4);
		expect(scratch.innerHTML, 'forceUpdate').to.equal(`<div><a>foo</a><b>bar</b><c>baz</c><b>bat</b></div>`);

		comp.alt = true;
		comp.forceUpdate();

		expect(scratch.firstChild.children, 'forceUpdate alt').to.have.length(4);
		expect(scratch.innerHTML, 'forceUpdate alt').to.equal(`<div><b>alt</b><a>foo</a><c>baz</c><b>bat</b></div>`);

		// Re-rendering from the root is non-destructive if the root was a previous render:
		comp.alt = false;
		render(<Foo />, scratch);

		expect(scratch.firstChild.children, 'root re-render').to.have.length(4);
		expect(scratch.innerHTML, 'root re-render').to.equal(`<div><a>foo</a><b>bar</b><c>baz</c><b>bat</b></div>`);

		comp.alt = true;
		render(<Foo />, scratch);

		expect(scratch.firstChild.children, 'root re-render 2').to.have.length(4);
		expect(scratch.innerHTML, 'root re-render 2').to.equal(`<div><b>alt</b><a>foo</a><c>baz</c><b>bat</b></div>`);

		render(
<Foo />
, scratch);

		expect(scratch.firstChild.children, 'root re-render changed').to.have.length(3);
		expect(scratch.innerHTML, 'root re-render changed').to.equal(`<div><div><a>foo</a><b>bar</b></div><c>baz</c><b>bat</b></div>`);
	});

	// Discussion: https://github.com/developit/preact/issues/287
	('HTMLDataListElement' in window ? it : xit)('should allow <input list /> to pass through as an attribute', () => {
		render((

				<input type="range" min="0" max="100" list="steplist" />
				<datalist id="steplist">
					<option>0</option>
					<option>50</option>
					<option>100</option>
				</datalist>

		), scratch);

		let html = scratch.firstElementChild.firstElementChild.outerHTML;
		expect(sortAttributes(html)).to.equal(sortAttributes('<input type="range" min="0" max="100" list="steplist">'));
	});

	it('should not execute append operation when child is at last', () => {
		// See developit/preact#717 for discussion about the issue this addresses

		let todoText = 'new todo that I should complete';
		let input;
		let setText;
		let addTodo;

		const ENTER = 13;

		class TodoList extends Component {
			constructor(props) {
				super(props);
				this.state = { todos: [], text: '' };
				setText = this.setText = this.setText.bind(this);
				addTodo = this.addTodo = this.addTodo.bind(this);
			}
			setText(e) {
				this.setState({ text: e.target.value });
			}
			addTodo(e) {
				if (e.keyCode === ENTER) {
					let { todos, text } = this.state;
					todos = todos.concat({ text });
					this.setState({ todos, text: '' });
				}
			}
			render() {
				const { todos, text } = this.state;
				return (

						{ todos.map( todo => ([
							{todo.text},
							 [ Delete ],

						])) }
						<input value={text} onInput={this.setText} ref={(i) => input = i} />

				);
			}
		}

		render(<TodoList />, scratch);

		// Simulate user typing
		input.focus();
		input.value = todoText;
		setText({
			target: input
		});

		// Commit the user typing setState call
		rerender();

		// Simulate user pressing enter
		addTodo({
			keyCode: ENTER
		});

		// Before Preact rerenders, focus should be on the input
		expect(document.activeElement).to.equal(input);

		rerender();

		// After Preact rerenders, focus should remain on the input
		expect(document.activeElement).to.equal(input);
		expect(scratch.innerHTML).to.contain(`<span>${todoText}</span>`);
	});
});


import { createElement as h, render, Component, createRef } from '../../src/index';
import { setupScratch, teardown, setupRerender } from '../_util/helpers';

/** @jsx h */

// gives call count and argument errors names (otherwise sinon just uses "spy"):
let spy = (name, ...args) => {
	let spy = sinon.spy(...args);
	spy.displayName = `spy('${name}')`;
	return spy;
};

describe('refs', () => {
	let scratch;
	let rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should invoke refs in render()', () => {
		let ref = spy('ref');
		render(
, scratch);
		expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild);
	});

	it('should support createRef', () => {
		const r = createRef();
		expect(r.current).to.equal(undefined);

		render(
, scratch);
		expect(r.current).to.equal(scratch.firstChild);
	});

	it('should invoke refs in Component.render()', () => {
		let outer = spy('outer'),
			inner = spy('inner');
		class Foo extends Component {
			render() {
				return (

				);
			}
		}
		render(<Foo />, scratch);

		expect(outer).to.have.been.calledWith(scratch.firstChild);
		expect(inner).to.have.been.calledWith(scratch.firstChild.firstChild);
	});

	it('should pass components to ref functions', () => {
		let ref = spy('ref'),
			instance;
		class Foo extends Component {
			constructor() {
				super();
				instance = this;
			}
			render() {
				return
;
			}
		}
		render(<Foo ref={ref} />, scratch);

		expect(ref).to.have.been.calledOnce.and.calledWith(instance);
	});

	it('should pass rendered DOM from functional components to ref functions', () => {
		let ref = spy('ref');

		const Foo = () =>
;

		render(<Foo ref={ref} />, scratch);
		expect(ref).to.have.been.calledOnce;

		ref.resetHistory();
		render(<Foo ref={ref} />, scratch);
		expect(ref).to.have.been.calledOnce;

		ref.resetHistory();
		render(, scratch);
		expect(ref).to.have.been.calledOnce.and.calledWith(null);
	});

	it('should pass children to ref functions', () => {
		let outer = spy('outer'),
			inner = spy('inner'),
			InnermostComponent = 'span',
			update, inst;
		class Outer extends Component {
			constructor() {
				super();
				update = () => this.forceUpdate();
			}
			render() {
				return (

						<Inner ref={outer} />

				);
			}
		}
		class Inner extends Component {
			constructor() {
				super();
				inst = this;
			}
			render() {
				return <InnermostComponent ref={inner} />;
			}
		}

		render(<Outer />, scratch);

		expect(outer).to.have.been.calledOnce.and.calledWith(inst);
		expect(inner).to.have.been.calledOnce.and.calledWith(inst.base);

		outer.resetHistory();
		inner.resetHistory();
		update();

		expect(outer, 're-render').to.have.been.calledOnce.and.calledWith(inst);
		expect(inner, 're-render').not.to.have.been.called;

		inner.resetHistory();
		InnermostComponent = 'x-span';
		update();

		expect(inner, 're-render swap');
		expect(inner.firstCall, 're-render swap').to.have.been.calledWith(null);
		expect(inner.secondCall, 're-render swap').to.have.been.calledWith(inst.base);

		InnermostComponent = 'span';
		outer.resetHistory();
		inner.resetHistory();
		render(
, scratch);

		expect(outer, 'unrender').to.have.been.calledOnce.and.calledWith(null);
		expect(inner, 'unrender').to.have.been.calledOnce.and.calledWith(null);
	});

	it('should pass high-order children to ref functions', () => {
		let outer = spy('outer'),
			inner = spy('inner'),
			innermost = spy('innermost'),
			InnermostComponent = 'span',
			outerInst,
			innerInst;
		class Outer extends Component {
			constructor() {
				super();
				outerInst = this;
			}
			render() {
				return <Inner ref={inner} />;
			}
		}
		class Inner extends Component {
			constructor() {
				super();
				innerInst = this;
			}
			render() {
				return <InnermostComponent ref={innermost} />;
			}
		}

		render(<Outer ref={outer} />, scratch);

		expect(outer, 'outer initial').to.have.been.calledOnce.and.calledWith(outerInst);
		expect(inner, 'inner initial').to.have.been.calledOnce.and.calledWith(innerInst);
		expect(innermost, 'innerMost initial').to.have.been.calledOnce.and.calledWith(innerInst.base);

		outer.resetHistory();
		inner.resetHistory();
		innermost.resetHistory();
		render(<Outer ref={outer} />, scratch);

		expect(outer, 'outer update').to.have.been.calledOnce.and.calledWith(outerInst);
		expect(inner, 'inner update').to.have.been.calledOnce.and.calledWith(innerInst);
		expect(innermost, 'innerMost update').not.to.have.been.called;

		innermost.resetHistory();
		InnermostComponent = 'x-span';
		render(<Outer ref={outer} />, scratch);

		expect(innermost, 'innerMost swap');
		expect(innermost.firstCall, 'innerMost swap').to.have.been.calledWith(null);
		expect(innermost.secondCall, 'innerMost swap').to.have.been.calledWith(innerInst.base);
		InnermostComponent = 'span';

		outer.resetHistory();
		inner.resetHistory();
		innermost.resetHistory();
		render(
, scratch);

		expect(outer, 'outer unmount').to.have.been.calledOnce.and.calledWith(null);
		expect(inner, 'inner unmount').to.have.been.calledOnce.and.calledWith(null);
		expect(innermost, 'innerMost unmount').to.have.been.calledOnce.and.calledWith(null);
	});

	// Test for #1143
	it('should not pass ref into component as a prop', () => {
		let foo = spy('foo'),
			bar = spy('bar');

		class Foo extends Component {
			render() {
				return
;
			}
		}
		const Bar = spy('Bar', () =>
);

		sinon.spy(Foo.prototype, 'render');

		render((

				<Foo ref={foo} a="a" />
				<Bar ref={bar} b="b" />

		), scratch);

		expect(Foo.prototype.render).to.have.been.calledWithMatch({ ref: sinon.match.falsy, a: 'a' }, { }, { });
		expect(Bar).to.have.been.calledWithMatch({ b: 'b', ref: sinon.match.falsy }, { });
	});

	// Test for #232
	it('should only null refs after unmount', () => {
		let outer, inner;

		class TestUnmount extends Component {
			componentWillUnmount() {
				expect(this).to.have.property('outer', outer);
				expect(this).to.have.property('inner', inner);

				setTimeout( () => {
					expect(this).to.have.property('outer', null);
					expect(this).to.have.property('inner', null);
				});
			}

			render() {
				return (

 this.outer=c}>

 this.inner=c} />

				);
			}
		}

		sinon.spy(TestUnmount.prototype, 'componentWillUnmount');

		render(
<TestUnmount />
, scratch);
		outer = scratch.querySelector('#outer');
		inner = scratch.querySelector('#inner');

		expect(TestUnmount.prototype.componentWillUnmount).not.to.have.been.called;

		render(
, scratch);
		expect(TestUnmount.prototype.componentWillUnmount).to.have.been.calledOnce;
	});

	it('should null and re-invoke refs when swapping component root element type', () => {
		let inst;

		class App extends Component {
			render() {
				return
<Child />
;
			}
		}

		class Child extends Component {
			constructor(props, context) {
				super(props, context);
				this.state = { show: false };
				inst = this;
			}
			handleMount(){}
			render(_, { show }) {
				if (!show) return
;
				return some test content;
			}
		}
		sinon.spy(Child.prototype, 'handleMount');

		render(<App />, scratch);
		expect(inst.handleMount).to.have.been.calledOnce.and.calledWith(scratch.querySelector('#div'));
		inst.handleMount.resetHistory();

		inst.setState({ show: true });
		rerender();
		expect(inst.handleMount).to.have.been.calledTwice;
		expect(inst.handleMount.firstCall).to.have.been.calledWith(null);
		expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#span'));
		inst.handleMount.resetHistory();

		inst.setState({ show: false });
		rerender();
		expect(inst.handleMount).to.have.been.calledTwice;
		expect(inst.handleMount.firstCall).to.have.been.calledWith(null);
		expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#div'));
	});

	it('should add refs to components representing DOM nodes with no attributes if they have been pre-rendered', () => {
		// Simulate pre-render
		let parent = document.createElement('div');
		let child = document.createElement('div');
		parent.appendChild(child);
		scratch.appendChild(parent); // scratch contains:

		let ref = spy('ref');

		class Wrapper extends Component {
			render() {
				return
;
			}
		}

		render(
<Wrapper ref={c => ref(c.base)} />
, scratch);
		expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild.firstChild);
	});

	// Test for #1177
	it('should call ref after children are rendered', done => {
		let input;
		function autoFocus(el) {
			if (el) {
				input = el;

				// Chrome bug: It will somehow drop the focus event if it fires too soon.
				// See https://stackoverflow.com/questions/17384464/
				setTimeout(() => {
					el.focus();
					done();
				}, 1);
			}
		}

		render(<input type="text" ref={autoFocus} value="foo" />, scratch);
		expect(input.value).to.equal('foo');
	});
});


import { createElement as h, Component } from '../../src';

/** @jsx h */

/**
 * Setup the test environment
 * @returns {HTMLDivElement}
 */
export function setupScratch() {
	const scratch = document.createElement('div');
	scratch.id = 'scratch';
	(document.body || document.documentElement).appendChild(scratch);
	return scratch;
}

/**
 * Setup a rerender function that will drain the queue of pending renders
 * @returns {() => void}
 */
export function setupRerender() {
	Component.__test__previousDebounce = Component.debounce;
	Component.debounce = cb => Component.__test__drainQueue = cb;

	return () => Component.__test__drainQueue && Component.__test__drainQueue();
}

/**
 * Teardown test environment and reset preact's internal state
 * @param {HTMLDivElement} scratch
 */
export function teardown(scratch) {
	scratch.parentNode.removeChild(scratch);

	if (Component.__test__drainQueue) {
		// Flush any pending updates leftover by test
		Component.__test__drainQueue();
		delete Component.__test__drainQueue;
	}

	if (typeof Component.__test__previousDebounce !== 'undefined') {
		Component.debounce = Component.__test__previousDebounce;
		delete Component.__test__previousDebounce;
	}
}

/**
 * A helper to generate innerHTML validation strings containing spans
 * @param {string} contents The contents of the span, as a string
 */
export const span = contents => `<span>${contents}</span>`;

/**
 * A helper to generate innerHTML validation strings containing divs
 * @param {string} contents The contents of the div, as a string
 */
export const div = contents => `<div>${contents}</div>`;

const Foo = () => 'd';
export const getMixedArray = () => (
	// Make it a function so each test gets a new copy of the array
	[0, 'a', 'b', c, <Foo />, null, undefined, false, ['e', 'f'], 1]
);
export const mixedArrayHTML = '0abcdef1';

/**
 * Serialize an object
 * @param {Object} obj
 * @return {string}
 */
export function serialize(obj) {
	if (obj instanceof Text) return '#text';
	if (obj instanceof Element) return `<${obj.localName}>${obj.textContent}`;
	if (obj === document) return 'document';
	return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '');
}

let log = {};

/**
 * Modify obj's original method to log calls and arguments on logger object
 * @param {object} obj
 * @param {string} method
 */
export function logCall(obj, method) {
	let old = obj[method];
	obj[method] = function() {
		let c = '';
		for (let i=0; i<arguments.length; i++) {
			if (c) c += ', ';
			c += serialize(arguments[i]);
		}
		const key = `${serialize(this)}.${method}(${c})`;
		log[key] = (log[key] || 0) + 1;
		return old.apply(this, arguments);
	};
}

/**
 * Return log object
 * @return {object} log
 */
export function getLog() {
	return log;
}

/** Clear log object */
export function clearLog() {
	log = {};
}

/**
 * Reset obj to empty to keep reference
 * @param {object} obj
 */
export function clear(obj) {
	Object.keys(obj).forEach(key => delete obj[key]);
}


(Wrapper.prototype = new Component()).constructor = Wrapper;

this.render = sinon.spy((props, state) => <div {...props} >{state.text}</div>);


import { Component } from 'ceviche';
import { select as d3select, mouse as d3mouse } from 'd3-selection';
import { scaleLinear } from 'd3-scale';
import Pythagoras from './pythagoras';

export default class PythagorasDemo extends Component {
	svg = {
		width: 1280,
		height: 600
	};

	state = {
		currentMax: 0,
		baseW: 80,
		heightFactor: 0,
		lean: 0
	};

	realMax = 11;

	svgRef = c => {
		this.svgElement = c;
	};

	scaleFactor = scaleLinear().domain([this.svg.height, 0]).range([0, .8]);

	scaleLean = scaleLinear().domain([0, this.svg.width/2, this.svg.width]).range([.5, 0, -.5]);

	onMouseMove = event => {
		let [x, y] = d3mouse(this.svgElement);

		this.setState({
			heightFactor: this.scaleFactor(y),
			lean: this.scaleLean(x)
		});
	};

	restart = () => {
		this.setState({ currentMax: 0 });
		this.next();
	};

	next = () => {
		let { currentMax } = this.state;

		if (currentMax < this.realMax) {
			this.setState({ currentMax: currentMax + 1 });
			this.timer = setTimeout(this.next, 500);
		}
	};

	componentDidMount() {
		this.selected = d3select(this.svgElement).on('mousemove', this.onMouseMove);
		this.next();
	}

	componentWillUnmount() {
		this.selected.on('mousemove', null);
		clearTimeout(this.timer);
	}

	render({ }, { currentMax, baseW, heightFactor, lean }) {
		let { width, height } = this.svg;

		return (
			<div class="App">
				<svg width={width} height={height} ref={this.svgRef}>
					<Pythagoras
						w={baseW}
						h={baseW}
						heightFactor={heightFactor}
						lean={lean}
						x={width/2-40}
						y={height-baseW}
						lvl={0}
						maxlvl={currentMax}
					/>
				</svg>
			</div>
		);
	}
}


.and.to.have.returned(sinon.match({ tag: 'div', props: PROPS }));

keep keep keep keep replace keep keep keep


import { createElement, Component } from 'ceviche';

let counter = 0;

export default class TodoList extends Component {
	state = { todos: [], text: '' };

	setText = e => {
		this.setState({ text: e.target.value });
	};

	addTodo = () => {
		let { todos, text } = this.state;
		todos = todos.concat({ text, id: ++counter });
		this.setState({ todos, text: '' });
	};

	removeTodo = e => {
		let id = e.target.getAttribute('data-id');
		this.setState({ todos: this.state.todos.filter( t => t.id!=id ) });
	};

	render({ }, { todos, text }) {
		return (
			<form onSubmit={this.addTodo} action="javascript:">
				<input value={text} onInput={this.setText} />
				<button type="submit">Add</button>

					<TodoItems todos={todos} removeTodo={this.removeTodo}/>

			</form>
		);
	}
}

class TodoItems extends Component {
	render({ todos, removeTodo }) {
		return todos.map( todo => (

				<button onClick={removeTodo} data-id={todo.id}>×</button>
				{' '}
				{todo.text}

		));
	}
}


/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/
/*eslint no-console:0*/
/** @jsx h */
import { setupScratch, teardown } from '../_util/helpers';
let { createElement: h, Component, render } = require(NODE_ENV==='production' ? '../../dist/ceviche.min.js' : '../../dist/ceviche');

const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999;

let now = typeof performance!=='undefined' && performance.now ? () => performance.now() : () => +new Date();

function loop(iter, time) {
	let start = now(),
		count = 0;
	while ( now()-start < time ) {
		count++;
		iter();
	}
	return count;
}

function benchmark(iter, callback) {
	let a = 0; // eslint-disable-line no-unused-vars
	function noop() {
		try { a++; }
		finally { a += Math.random(); }
	}

	// warm
	for (let i=100; i--; ) noop(), iter();

	let count = 2,
		time = 500,
		passes = 0,
		noops = loop(noop, time),
		iterations = 0;

	function next() {
		iterations += loop(iter, time);
		setTimeout(++passes===count ? done : next, 10);
	}

	function done() {
		let ticks = Math.round(noops / iterations * count),
			hz = iterations / count / time * 1000,
			message = `${hz|0}/s (${ticks} ticks)`;
		callback({ iterations, noops, count, time, ticks, hz, message });
	}

	next();
}

describe('performance', function() {
	let scratch;

	this.timeout(10000);

	before(function () {
		if (!ENABLE_PERFORMANCE) this.skip();
		if (coverage) {
			console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.');
		}
	});

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should rerender without changes fast', done => {
		let jsx = (

a {'b'} c {0} d

						Foo
						Bar

					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<button-bar>
							<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
							<button style="top:0 ; right: 20">Poor CSS</button>
							<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
							<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
						</button-bar>
					</form>

		);

		let root;
		benchmark( () => {
			root = render(jsx, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: empty diff: ${message}`);
			expect(ticks).to.be.below(150 * MULTIPLIER);
			done();
		});
	});

	it('should rerender repeated trees fast', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		class ButtonBar extends Component {
			render() {
				return (
					<button-bar>
						<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
						<Button style="top:0 ; right: 20">Poor CSS</Button>
						<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
						<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
					</button-bar>
				);
			}
		}
		class Button extends Component {
			render(props) {
				return <button {...props} />;
			}
		}
		class Main extends Component {
			render() {
				return <Form />;
			}
		}
		class Root extends Component {
			render() {
				return (

				);
			}
		}
		class Empty extends Component {
			render() {
				return
;
			}
		}
		class Parent extends Component {
			render({ child: C }) {
				return <C />;
			}
		}

		let root;
		benchmark( () => {
			root = render(<Parent child={Root} />, scratch, root);
			root = render(<Parent child={Empty} />, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: repeat diff: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should construct large VDOM trees fast', done => {
		const FIELDS = [];
		for (let i=100; i--; ) FIELDS.push((i*999).toString(36));

		let out = [];
		function digest(vnode) {
			out.push(vnode);
			out.length = 0;
		}
		benchmark( () => {
			digest(

a {'b'} c {0} d

							Foo
							Bar

						<form onSubmit={() => {}}>
							<input type="checkbox" checked />
							<input type="checkbox" />
							<fieldset>
								{ FIELDS.map( field => (

										{field}:
										<input placeholder={field} />

								)) }
							</fieldset>
							<button-bar>
								<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
								<button style="top:0 ; right: 20">Poor CSS</button>
								<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
								<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
							</button-bar>
						</form>

			);
		}, ({ ticks, message }) => {
			console.log(`PERF: large VTree: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should mutate styles/properties quickly', done => {
		let counter = 0;
		const keyLooper = n => c => c % n ? `${c}px` : c;
		const get = (obj, i) => obj[i%obj.length];
		const CLASSES = ['foo', 'foo bar', '', 'baz-bat', null];
		const STYLES = [];
		const MULTIVALUE = ['0 1px', '0 0 1px 0', '0', '1px', '20px 10px', '7em 5px', '1px 0 5em 2px'];
		const STYLEKEYS = [
			['left', keyLooper(3)],
			['top', keyLooper(2)],
			['margin', c => get(MULTIVALUE, c).replace('1px', c+'px')],
			['padding', c => get(MULTIVALUE, c)],
			['position', c => c%5 ? c%2 ? 'absolute' : 'relative' : null],
			['display', c => c%10 ? c%2 ? 'block' : 'inline' : 'none'],
			['color', c => `rgba(${c%255}, ${255 - c%255}, ${50+c%150}, ${c%50/50})`],
			['border', c => c%5 ? `${c%10}px ${c%2?'solid':'dotted'} ${STYLEKEYS[6][1](c)}` : '']
		];
		for (let i=0; i<1000; i++) {
			let style = {};
			for (let j=0; j (

				<input type="checkbox" checked={index%3 == 0} />
				<input value={`test ${index/4|0}`} disabled={index%10 ? null : true} />

p1

p2

p3

p4

		);

		let root, count=0;
		benchmark( () => {
			root = render(app(++count), scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: style/prop mutation: ${message}`);
			expect(ticks).to.be.below(350 * MULTIPLIER);
			done();
		});
	});

	it('should hydrate from SSR quickly', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		const ButtonBar = () => (
			<button-bar>
				<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
				<Button style="top:0 ; right: 20">Poor CSS</Button>
				<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
				<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
			</button-bar>
		);
		class Button extends Component {
			handleClick() {}
			render(props) {
				return <button onClick={this.handleClick} {...props} />;
			}
		}
		const Main = () => <Form />;
		class App extends Component {
			render() {
				return (

				);
			}
		}

		render(<App />, scratch, scratch.firstChild);
		let html = scratch.innerHTML;

		benchmark( () => {
			scratch.innerHTML = html;
			render(<App />, scratch, scratch.firstChild);
		}, ({ ticks, message }) => {
			console.log(`PERF: SSR Hydrate: ${message}`);
			expect(ticks).to.be.below(3000 * MULTIPLIER);
			done();
		});
	});
});


class Foo {
constructor() {
instance = this;
this.state = STATE;
}
render() {
return <div />;
}
}

keep keep keep keep keep replace replace keep keep


node_modules
npm-debug.log
dist
package-lock.json
.vscode
.idea
test/ts/**/*.js
coverage


import { assign } from './util';
import { diff, flushMounts } from './diff/index';
// import { diff, diffLevel } from './diff/index';

/**
 * Base Component class. Provides `setState()` and `forceUpdate()`, which
 * trigger rendering
 * @param {object} props The initial component props
 * @param {object} context The initial context from parent components'
 * getChildContext
 */
export function Component(props, context) {
	this.props = props;
	this.context = context;
	// if (this.state==null) this.state = {};
	this.state = {};
	this._dirty = true;
	this._renderCallbacks = []; // Only class components

	// Other properties that Component will have set later,
	// shown here as commented out for quick reference
	// this.base = null;
	// this._ancestorComponent = null; // Always set right after instantiation
	// this._vnode = null;
	// this._nextState = null; // Only class components
	// this._previousVTree = null;
	// this._processingException = null; // Always read, set only when handling error
	// this._constructor = null; // Only functional components, always set right after instantiation
}

/**
 * Update component state and schedule a re-render.
 * @param {object | ((s: object, p: object) => object)} update A hash of state
 * properties to update with new values or a function that given the current
 * state and props returns a new partial state
 * @param {() => void} [callback] A function to be called once component state is
 * updated
 */
Component.prototype.setState = function(update, callback) {
	// if (this.prevState==null) this.prevState = assign({}, this.state);
	// let s = this._nextState || this.state;
	// let s = this.state;
	// assign(this.state, update);

	// only clone state when copying to nextState the first time.
	let s = this._nextState || (this._nextState = assign({}, this.state));

	// if update() mutates state in-place, skip the copy:
	if (typeof update!=='function' || (update = update(s, this.props))) {
		assign(s, update);
	}

	// Skip update if updater function returned null
	if (update==null) return;

	// let s = this._nextState || this.state;
	// this._nextState = assign(assign({}, s), typeof update==='function' && update(s, this.props) || s);

	// console.log(update, this._nextState);
	// this._nextState = Object.assign({}, s, update);
	if (callback!=null) this._renderCallbacks.push(callback);

	this._force = false;
	enqueueRender(this);
	// if (!this._dirty && (this._dirty = true) && q.push(this)===1) (0, Component.debounce)(process);
};

/**
 * Immediately perform a synchronous re-render of the component
 * @param {() => void} [callback] A function to be called after component is
 * re-renderd
 */
Component.prototype.forceUpdate = function(callback) {
	if (this._parent!=null) {
		// Set render mode so that we can differantiate where the render request
		// is coming from. We need this because forceUpdate should never call
		// shouldComponentUpdate
		if (this._force==null) this._force = true;

		let mounts = [];
		diff(this._vnode._el, this._parent, this._vnode, this._vnode, this.context, this._parent.ownerSVGElement!==undefined, true, null, mounts, this._ancestorComponent, this._parentVNode || {});
		flushMounts(mounts);

		// Reset mode to its initial value for the next render
		this._force = null;
	}
	if (callback!=null) callback();
};

/**
 * The render queue
 * @type {Array<import('./internal').Component>}
 */
let q = [];

// const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
// Component.debounce = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;

/**
 * Asynchronously schedule a callback
 * @type {(cb) => void}
 */
const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
// Component.debounce = setTimeout;

/**
 * Enqueue a rerender of a component
 * @param {import('./internal').Component} c The component to rerender
 */
export function enqueueRender(c) {
	// console.log('enqueueRender', c.id, q.length===0, c._dirty);
	if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
		// (0, Component.debounce)(process);
		(Component.debounce || defer)(process);
		// (Component.debounce || setTimeout)(process);
		// defer(process);
		// (Component.debounce || setTimeout)(process);
		// (0, Component.debounce || defer)(process);
	}

	// if (!c._dirty) {
	// 	c._dirty = true;
	// 	if (diffLevel!==0) {
	// 		q.push(c);
	// 	}
	// 	else {
	// 		diff(c.base, c.base.parentNode, c._vnode, c._vnode, c.context);
	// 	}
	// }

	// if (!c._dirty && (c._dirty = true) && q.push(c) === 1) {
	// 	requestAnimationFrame(process);
	// }
	// if (q.push(c) === 1) setTimeout(process);
}

/** Flush the render queue by rerendering all queued components */
function process() {
	// console.log('process queue', q.map(c => c.context.__depth)+' ');

	// requestIdleCallback( (c) => {
	// 	let p;
	// 	while (c.timeRemaining()>0 && (p=q.pop())) {
	// 		if (p._dirty) p.forceUpdate();
	// 	}
	// 	if (q.length) process();
	// });

	let p;
	while ((p=q.pop())) {
		if (p._dirty) p.forceUpdate();
	}

	// let time = Date.now(), len=q.length, i=len;
	// while (i--) {
	// 	if (Date.now() - time > 2) break;
	// 	if (q[i]._dirty) q[i].forceUpdate();
	// }
	// q.splice(i, len-i);
	// if (i>=0) (0, Component.debounce)(process);

	// while ( Date.now() - time < 5 && (p=q.pop())) {
	// 	if (p._dirty) p.forceUpdate();
	// }
	// if (q.length) (0, Component.debounce)(process);

	// let current = q;
	// q = [];
	// for (let i=current.length; i--; ) {
	// 	if (current[i]._dirty) {
	// 		current[i].forceUpdate();
	// 	}
	// }

	// let current = q, p;
	// q = [];
	// while ((p = current.pop())) {
	// 	if (p._dirty) {
	// 		p.forceUpdate();
	// 		// diff(p.base, p.base.parentNode, p._vnode, p._vnode, p.context);
	// 	}
	// }
}

// export { process as processQueue };


export const EMPTY_OBJ = {};
export const EMPTY_ARR = [];
export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;


import { createElement, cloneElement } from '../../src/index';

/** @jsx createElement */

describe('cloneElement', () => {
	it('should clone components', () => {
		function Comp () {}
		const instance = <Comp prop1={1}>hello</Comp>;
		const clone = cloneElement(instance);

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.tag).to.equal(instance.tag);
		expect(clone.props).not.to.equal(instance.props); // Should be a different object...
		expect(clone.props).to.deep.equal(instance.props); // with the same properties
	});

	it('should merge new props', () => {
		function Foo () {}
		const instance = <Foo prop1={1} prop2={2} />;
		const clone = cloneElement(instance, { prop1: -1, newProp: -2 });

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.tag).to.equal(instance.tag);
		expect(clone.props).not.to.equal(instance.props);
		expect(clone.props.prop1).to.equal(-1);
		expect(clone.props.prop2).to.equal(2);
		expect(clone.props.newProp).to.equal(-2);
	});

	it('should override children if specified', () => {
		function Foo() {}
		const instance = <Foo>hello</Foo>;
		const clone = cloneElement(instance, null, 'world', '!');

		expect(clone.prototype).to.equal(instance.prototype);
		expect(clone.tag).to.equal(instance.tag);
		expect(clone.props).not.to.equal(instance.props);
		expect(clone.props.children).to.deep.equal(['world', '!']);
	});
});


// function createRoot(title) {
// 	let div = document.createElement('div');
// 	let h2 = document.createElement('h2');
// 	h2.textContent = title;
// 	div.appendChild(h2);
// 	document.body.appendChild(div);
// 	return div;
// }

/*
function logCall(obj, method, name) {
	let old = obj[method];
	obj[method] = function(...args) {
		console.log(`<${this.localName}>.`+(name || `${method}(${args})`));
		return old.apply(this, args);
	};
}

logCall(HTMLElement.prototype, 'appendChild');
logCall(HTMLElement.prototype, 'removeChild');
logCall(HTMLElement.prototype, 'insertBefore');
logCall(HTMLElement.prototype, 'replaceChild');
logCall(HTMLElement.prototype, 'setAttribute');
logCall(HTMLElement.prototype, 'removeAttribute');
let d = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');
Object.defineProperty(Text.prototype, 'nodeValue', {
	get() {
		let value = d.get.call(this);
		console.log('get Text#nodeValue: ', value);
		return value;
	},
	set(v) {
		console.log('set Text#nodeValue', v);
		return d.set.call(this, v);
	}
});

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

class Foo extends Component {
	componentDidMount() {
		console.log('mounted');
		this.timer = setInterval( () => {
			this.setState({ time: Date.now() });
		}, 5000);
	}
	componentWillUnmount() {
		clearInterval(this.timer);
	}
	render(props, state, context) {
		// console.log('rendering', props, state, context);
		return <time>test: {state.time}</time>
	}
}

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

let items = [];
let count = 0;
let three = createRoot('Top-level render demo:');

setInterval( () => {
	if (count++ %20 < 10 ) {
		items.push(
item #{items.length}
);
	}
	else {
		items.shift();
	}

	render((

This is a test.

			<time>{Date.now()}</time>

{items}

	), three);
}, 5000);

// Mount the top-level component to the DOM:
render(, document.body);
*/


usage: 'vue-cli-service inspect [options] [...paths]',

'--mode': 'specify env mode (default: development)',

'--rule': 'inspect a specific module rule',

'--plugin': 'inspect a specific plugin',

'--verbose': 'show full function definitions in output'

keep keep keep replace replace keep keep keep

'--rule <ruleName>': 'inspect a specific module rule',

'--plugin <pluginName>': 'inspect a specific plugin',

'--rules': 'list all module rule names',

#!/usr/bin/env node

const fs = require('fs')
const path = require('path')
const slash = require('slash')
const chalk = require('chalk')
const semver = require('semver')
const minimist = require('minimist')
const requiredVersion = require('../package.json').engines.node

if (!semver.satisfies(process.version, requiredVersion)) {
  console.log(chalk.red(
    `You are using Node ${process.version}, but this version of vue-cli ` +
    `requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
  ))
  process.exit(1)
}

// enter debug mode when creating test repo
if (
  slash(process.cwd()).indexOf('/packages/test') > 0 && (
    fs.existsSync(path.resolve(process.cwd(), '../@vue')) ||
    fs.existsSync(path.resolve(process.cwd(), '../../@vue'))
  )
) {
  process.env.VUE_CLI_DEBUG = true
}

const program = require('commander')
const loadCommand = require('../lib/util/loadCommand')

program
  .version(require('../package').version)
  .usage('<command> [options]')

program
  .command('create <app-name>')
  .description('create a new project powered by vue-cli-service')
  .option('-p, --preset <presetName>', 'Skip prompts and use saved or remote preset')
  .option('-d, --default', 'Skip prompts and use default preset')
  .option('-i, --inlinePreset <json>', 'Skip prompts and use inline JSON string as preset')
  .option('-m, --packageManager <command>', 'Use specified npm client when installing dependencies')
  .option('-r, --registry <url>', 'Use specified npm registry when installing dependencies (only for npm)')
  .option('-g, --git [message]', 'Force / skip git intialization, optionally specify initial commit message')
  .option('-f, --force', 'Overwrite target directory if it exists')
  .option('-c, --clone', 'Use git clone when fetching remote preset')
  .option('-x, --proxy', 'Use specified proxy when creating project')
  .action((name, cmd) => {
    require('../lib/create')(name, cleanArgs(cmd))
  })

program
  .command('add <plugin> [pluginOptions]')
  .allowUnknownOption()
  .description('install a plugin and invoke its generator in an already created project')
  .action((plugin) => {
    require('../lib/add')(plugin, minimist(process.argv.slice(3)))
  })

program
  .command('invoke <plugin> [pluginOptions]')
  .allowUnknownOption()
  .description('invoke the generator of a plugin in an already created project')
  .action((plugin) => {
    require('../lib/invoke')(plugin, minimist(process.argv.slice(3)))
  })

program
  .command('inspect [paths...]')
  .option('--mode <mode>')
  .option('--rule <ruleName>', 'inspect a specific module rule')
  .option('--plugin <pluginName>', 'inspect a specific plugin')
  .option('-v --verbose', 'Show full function definitions in output')
  .description('inspect the webpack config in a project with vue-cli-service')
  .action((paths, cmd) => {
    require('../lib/inspect')(paths, cleanArgs(cmd))
  })

program
  .command('serve [entry]')
  .description('serve a .js or .vue file in development mode with zero config')
  .option('-o, --open', 'Open browser')
  .action((entry, cmd) => {
    loadCommand('serve', '@vue/cli-service-global').serve(entry, cleanArgs(cmd))
  })

program
  .command('build [entry]')
  .option('-t, --target <target>', 'Build target (app | lib | wc | wc-async, default: app)')
  .option('-n, --name <name>', 'name for lib or web-component mode (default: entry filename)')
  .option('-d, --dest <dir>', 'output directory (default: dist)')
  .description('build a .js or .vue file in production mode with zero config')
  .action((entry, cmd) => {
    loadCommand('build', '@vue/cli-service-global').build(entry, cleanArgs(cmd))
  })

program
  .command('init <template> <app-name>')
  .description('generate a project from a remote template (legacy API, requires @vue/cli-init)')
  .option('-c, --clone', 'Use git clone when fetching remote template')
  .action(() => {
    loadCommand('init', '@vue/cli-init')
  })

// output help information on unknown commands
program
  .arguments('<command>')
  .action((cmd) => {
    program.outputHelp()
    console.log(`  ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`))
    console.log()
  })

// add some useful info on help
program.on('--help', () => {
  console.log()
  console.log(`  Run ${chalk.cyan(`vue <command> --help`)} for detailed usage of given command.`)
  console.log()
})

program.commands.forEach(c => c.on('--help', () => console.log()))

// enhance common error messages
const enhanceErrorMessages = (methodName, log) => {
  program.Command.prototype[methodName] = function (...args) {
    if (methodName === 'unknownOption' && this._allowUnknownOption) {
      return
    }
    this.outputHelp()
    console.log(`  ` + chalk.red(log(...args)))
    console.log()
    process.exit(1)
  }
}

enhanceErrorMessages('missingArgument', argName => {
  return `Missing required argument ${chalk.yellow(`<${argName}>`)}.`
})

enhanceErrorMessages('unknownOption', optionName => {
  return `Unknown option ${chalk.yellow(optionName)}.`
})

enhanceErrorMessages('optionMissingArgument', (option, flag) => {
  return `Missing required argument for option ${chalk.yellow(option.flags)}` + (
    flag ? `, got ${chalk.yellow(flag)}` : ``
  )
})

program.parse(process.argv)

if (!process.argv.slice(2).length) {
  program.outputHelp()
}

// commander passes the Command object itself as options,
// extract only actual options into a fresh object.
function cleanArgs (cmd) {
  const args = {}
  cmd.options.forEach(o => {
    const key = o.long.replace(/^--/, '')
    // if an option is not present and Command has a method with the same name
    // it should not be copied
    if (typeof cmd[key] !== 'function') {
      args[key] = cmd[key]
    }
  })
  return args
}


import './setPublicPath'
import mod from '~entry'
export default mod
export * from '~entry'


// https://docs.cypress.io/api/introduction/api.html

describe('My First Test', () => {
  it('Visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Welcome to Your Vue.js <%- hasTS ? '+ TypeScript ' : '' %>App')
  })
})


res = config.module.rules.find(r => r.__ruleName === args.rule)

res = config.plugins.find(p => p.__pluginName === args.plugin)

res = config.module.rules.map(r => r.__ruleName)

res = config.plugins.map(p => p.__pluginName)


module.exports = (api, options) => {
  api.chainWebpack(webpackConfig => {
    if (process.env.NODE_ENV === 'production') {
      webpackConfig
        .mode('production')
        .devtool('source-map')
        .output
          .filename(`js/[name].[chunkhash:8].js`)
          .chunkFilename(`js/[name].[chunkhash:8].js`)

      // keep module.id stable when vendor modules does not change
      webpackConfig
        .plugin('hash-module-ids')
          .use(require('webpack/lib/HashedModuleIdsPlugin'))

      // disable optimization during tests to speed things up
      if (process.env.VUE_CLI_TEST) {
        webpackConfig.optimization.minimize(false)
      } else {
        const UglifyPlugin = require('uglifyjs-webpack-plugin')
        const uglifyOptions = require('./uglifyOptions')
        webpackConfig.optimization.minimizer([
          new UglifyPlugin(uglifyOptions(options))
        ])
      }
    }
  })
}


jest.mock('fs')
jest.mock('inquirer')

const assertPromptModule = require('@vue/cli-test-utils/assertPromptModule')

const moduleToTest = require('../cssPreprocessors')

test('CSS pre-processor ', async () => {
  const expectedPrompts = [
    {
      message: 'features',
      choices: ['CSS Pre-processors'],
      check: [0]
    },
    {
      message: 'Pick a CSS pre-processor',
      choices: ['SASS', 'LESS', 'Stylus'],
      choose: 0
    }
  ]

  const expectedOptions = {
    cssPreprocessor: 'sass',
    plugins: {}
  }

  await assertPromptModule(
    moduleToTest,
    expectedPrompts,
    expectedOptions,
    { pluginsOnly: true }
  )
})


.option('--rule <ruleName>', 'inspect a specific module rule')

.option('--plugin <pluginName>', 'inspect a specific plugin')

.option('-v --verbose', 'Show full function definitions in output')

.description('inspect the webpack config in a project with vue-cli-service')

keep keep keep keep add keep keep keep keep

.option('--rules', 'list all module rule names')

.option('--plugins', 'list all plugin names')


module.exports = function fetchRemotePreset (name, clone) {
  // github shorthand fastpath
  if (!clone && /^[\w_-]+\/[\w_-]+$/.test(name)) {
    const { get } = require('./request')
    return get(`https://raw.githubusercontent.com/${name}/master/preset.json`)
      .then(res => res.body)
  }

  // fallback to full download
  const os = require('os')
  const path = require('path')
  const download = require('download-git-repo')
  const tmpdir = path.join(os.tmpdir(), 'vue-cli')

  return new Promise((resolve, reject) => {
    download(name, tmpdir, { clone }, err => {
      if (err) return reject(err)
      let preset
      try {
        preset = require(path.join(tmpdir, 'preset.json'))
      } catch (e) {
        return reject(e)
      }
      resolve(preset)
    })
  })
}


const chalk = require('chalk')

const rules = [
  {
    type: 'cant-resolve-loader',
    re: /Can't resolve '(.*loader)'/,
    msg: (e, match) => (
      `Failed to resolve loader: ${chalk.yellow(match[1])}\n` +
      `You may need to install it.`
    )
  }
]

exports.transformer = error => {
  if (error.webpackError) {
    const message = typeof error.webpackError === 'string'
      ? error.webpackError
      : error.webpackError.message || ''
    for (const { re, msg, type } of rules) {
      const match = message.match(re)
      if (match) {
        return Object.assign({}, error, {
          // type is necessary to avoid being printed as defualt error
          // by friendly-error-webpack-plugin
          type,
          shortMessage: msg(error, match)
        })
      }
    }
    // no match, unknown webpack error withotu a message.
    // friendly-error-webpack-plugin fails to handle this.
    if (!error.message) {
      return Object.assign({}, error, {
        type: 'unknown-webpack-error',
        shortMessage: message
      })
    }
  }
  return error
}

exports.formatter = errors => {
  errors = errors.filter(e => e.shortMessage)
  if (errors.length) {
    return errors.map(e => e.shortMessage)
  }
}


jest.setTimeout(15000)

const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')

let server, browser, page
test('build as wc in async mode', async () => {
  const project = await create('build-wc-async', defaultPreset)

  const { stdout } = await project.run(`vue-cli-service build --target wc-async **/*.vue`)
  expect(stdout).toMatch('Build complete.')

  expect(project.has('dist/demo.html')).toBe(true)
  expect(project.has('dist/build-wc-async.js')).toBe(true)
  expect(project.has('dist/build-wc-async.min.js')).toBe(true)

  // code-split chunks
  expect(project.has('dist/build-wc-async.0.js')).toBe(true)
  expect(project.has('dist/build-wc-async.0.min.js')).toBe(true)
  expect(project.has('dist/build-wc-async.1.js')).toBe(true)
  expect(project.has('dist/build-wc-async.1.min.js')).toBe(true)

  const port = await portfinder.getPortPromise()
  server = createServer({ root: path.join(project.dir, 'dist') })

  await new Promise((resolve, reject) => {
    server.listen(port, err => {
      if (err) return reject(err)
      resolve()
    })
  })

  const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
  browser = launched.browser
  page = launched.page

  const styleCount = await page.evaluate(() => {
    return document.querySelector('build-wc-async-app').shadowRoot.querySelectorAll('style').length
  })
  expect(styleCount).toBe(2) // should contain styles from both app and child

  const h1Text = await page.evaluate(() => {
    return document.querySelector('build-wc-async-app').shadowRoot.querySelector('h1').textContent
  })
  expect(h1Text).toMatch('Welcome to Your Vue.js App')

  const childStyleCount = await page.evaluate(() => {
    return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelectorAll('style').length
  })
  expect(childStyleCount).toBe(1)

  const h3Text = await page.evaluate(() => {
    return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelector('h3').textContent
  })
  expect(h3Text).toMatch('Installed CLI Plugins')
})

afterAll(async () => {
  await browser.close()
  server.close()
})