text: import { setupRerender } from 'preact/test-utils';
  import { createElement, render, Component, Fragment } from 'preact';
  import { setupScratch, teardown } from '../_util/helpers';
  import { span, div, ul, ol, li, section } from '../_util/dom';
  import { logCall, clearLog, getLog } from '../_util/logCall';
  
  /** @jsx createElement */
  /* eslint-disable react/jsx-boolean-value */
  
  describe('Fragment', () => {
  	let expectDomLog = false;
  
  	/** @type {HTMLDivElement} */
  	let scratch;
  
  	/** @type {() => void} */
  	let rerender;
  
  	let ops = [];
  
  	function expectDomLogToBe(expectedOperations, message) {
  		if (expectDomLog) {
  			expect(getLog()).to.deep.equal(expectedOperations, message);
  		}
  	}
  
  	class Stateful extends Component {
  		componentDidUpdate() {
  			ops.push('Update Stateful');
  		}
  		render() {
  			return <div>Hello</div>;
  		}
  	}
  
  	let resetInsertBefore;
  	let resetAppendChild;
  	let resetRemoveChild;
  
  	before(() => {
  		resetInsertBefore = logCall(Element.prototype, 'insertBefore');
  		resetAppendChild = logCall(Element.prototype, 'appendChild');
  		resetRemoveChild = logCall(Element.prototype, 'removeChild');
  		// logCall(CharacterData.prototype, 'remove');
  		// TODO: Consider logging setting set data
  		// ```
  		// var orgData = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data')
  		// Object.defineProperty(CharacterData.prototype, 'data', {
  		// 	...orgData,
  		// 	get() { return orgData.get.call(this) },
  		// 	set(value) { console.log('setData', value); orgData.set.call(this, value); }
  		// });
  		// ```
  	});
  
  	after(() => {
  		resetInsertBefore();
  		resetAppendChild();
  		resetRemoveChild();
  	});
  
  	beforeEach(() => {
  		scratch = setupScratch();
  		rerender = setupRerender();
  		ops = [];
  
  		clearLog();
  	});
  
  	afterEach(() => {
  		teardown(scratch);
  	});
  
  	it('should not render empty Fragment', () => {
  		render(<Fragment />, scratch);
  		expect(scratch.innerHTML).to.equal('');
  	});
  
  	it('should render a single child', () => {
  		clearLog();
  		render(
  			<Fragment>
  				<span>foo</span>
  			</Fragment>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.equal('<span>foo</span>');
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>.appendChild(<span>foo)'
  		]);
  	});
  
  	it('should render multiple children via noop renderer', () => {
  		render(
  			<Fragment>
  				hello <span>world</span>
  			</Fragment>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.equal('hello <span>world</span>');
  	});
  
  	it('should not crash with null as last child', () => {
  		let fn = () => {
  			render(
  				<Fragment>
  					<span>world</span>
  					{null}
  				</Fragment>,
  				scratch
  			);
  		};
  		expect(fn).not.to.throw();
  		expect(scratch.innerHTML).to.equal('<span>world</span>');
  
  		render(
  			<Fragment>
  				<span>world</span>
  				<p>Hello</p>
  			</Fragment>,
  			scratch
  		);
  		expect(scratch.innerHTML).to.equal('<span>world</span><p>Hello</p>');
  
  		expect(fn).not.to.throw();
  		expect(scratch.innerHTML).to.equal('<span>world</span>');
  
  		render(
  			<Fragment>
  				<span>world</span>
  				{null}
  				<span>world</span>
  			</Fragment>,
  			scratch
  		);
  		expect(scratch.innerHTML).to.equal('<span>world</span><span>world</span>');
  
  		render(
  			<Fragment>
  				<span>world</span>
  				Hello
  				<span>world</span>
  			</Fragment>,
  			scratch
  		);
  		expect(scratch.innerHTML).to.equal(
  			'<span>world</span>Hello<span>world</span>'
  		);
  	});
  
  	it('should handle reordering components that return Fragments #1325', () => {
  		class X extends Component {
  			render() {
  				return <Fragment>{this.props.children}</Fragment>;
  			}
  		}
  
  		class App extends Component {
  			render(props) {
  				if (this.props.i === 0) {
  					return (
  						<div>
  							<X key={1}>1</X>
  							<X key={2}>2</X>
  						</div>
  					);
  				}
  				return (
  					<div>
  						<X key={2}>2</X>
  						<X key={1}>1</X>
  					</div>
  				);
  			}
  		}
  
  		render(<App i={0} />, scratch);
  		expect(scratch.textContent).to.equal('12');
  		render(<App i={1} />, scratch);
  		expect(scratch.textContent).to.equal('21');
  	});
  
  	it('should handle changing node type within a Component that returns a Fragment #1326', () => {
  		class X extends Component {
  			render() {
  				return this.props.children;
  			}
  		}
  
  		/** @type {(newState: any) => void} */
  		let setState;
  		class App extends Component {
  			constructor(props, context) {
  				super(props, context);
  
  				this.state = { i: 0 };
  				setState = this.setState.bind(this);
  			}
  
  			render() {
  				if (this.state.i === 0) {
  					return (
  						<div>
  							<X>
  								<span>1</span>
  							</X>
  							<X>
  								<span>2</span>
  								<span>2</span>
  							</X>
  						</div>
  					);
  				}
  
  				return (
  					<div>
  						<X>
  							<div>1</div>
  						</X>
  						<X>
  							<span>2</span>
  							<span>2</span>
  						</X>
  					</div>
  				);
  			}
  		}
  
  		render(<App />, scratch);
  		expect(scratch.innerHTML).to.equal(div([span(1), span(2), span(2)]));
  
  		setState({ i: 1 });
  
  		clearLog();
  		rerender();
  
  		expect(scratch.innerHTML).to.equal(div([div(1), span(2), span(2)]));
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>122.insertBefore(<div>1, <span>1)',
  			'<span>1.remove()'
  		]);
  	});
  
  	it('should preserve state of children with 1 level nesting', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Stateful key="a" />
  			) : (
  				<Fragment>
  					<Stateful key="a" />
  					<div key="b">World</div>
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.deep.equal('<div>Hello</div><div>World</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.deep.equal('<div>Hello</div>');
  	});
  
  	it('should preserve state between top-level fragments', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>
  					<Stateful />
  				</Fragment>
  			) : (
  				<Fragment>
  					<Stateful />
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([]);
  	});
  
  	it('should preserve state of children nested at same level', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>
  					<Fragment>
  						<Fragment>
  							<Stateful key="a" />
  						</Fragment>
  					</Fragment>
  				</Fragment>
  			) : (
  				<Fragment>
  					<Fragment>
  						<Fragment>
  							<div />
  							<Stateful key="a" />
  						</Fragment>
  					</Fragment>
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div></div><div>Hello</div>');
  		expectDomLogToBe(['<div>Hello.insertBefore(<div>, <div>Hello)']);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe(['<div>.remove()']);
  	});
  
  	it('should not preserve state in non-top-level fragment nesting', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>
  					<Fragment>
  						<Stateful key="a" />
  					</Fragment>
  				</Fragment>
  			) : (
  				<Fragment>
  					<Stateful key="a" />
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>Hello.insertBefore(<div>Hello, <div>Hello)',
  			'<div>Hello.remove()'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			// Re-append the Stateful DOM since it has been re-parented
  			'<div>Hello.insertBefore(<div>Hello, <div>Hello)',
  			'<div>Hello.remove()'
  		]);
  	});
  
  	it('should not preserve state of children if nested 2 levels without siblings', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Stateful key="a" />
  			) : (
  				<Fragment>
  					<Fragment>
  						<Stateful key="a" />
  					</Fragment>
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>Hello.insertBefore(<div>Hello, <div>Hello)',
  			'<div>Hello.remove()'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>Hello.insertBefore(<div>Hello, <div>Hello)',
  			'<div>Hello.remove()'
  		]);
  	});
  
  	it('should just render children for fragments', () => {
  		class Comp extends Component {
  			render() {
  				return (
  					<Fragment>
  						<div>Child1</div>
  						<div>Child2</div>
  					</Fragment>
  				);
  			}
  		}
  
  		render(<Comp />, scratch);
  		expect(scratch.innerHTML).to.equal('<div>Child1</div><div>Child2</div>');
  	});
  
  	it('should not preserve state of children if nested 2 levels with siblings', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Stateful key="a" />
  			) : (
  				<Fragment>
  					<Fragment>
  						<Stateful key="a" />
  					</Fragment>
  					<div />
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div><div></div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should preserve state between array nested in fragment and fragment', () => {
  		// In this test case, the children of the Fragment in Foo end up being the same when flattened.
  		//
  		// When condition == true, the children of the Fragment are a Stateful VNode.
  		// When condition == false, the children of the Fragment are an Array containing a single
  		// Stateful VNode.
  		//
  		// However, when each of these are flattened (in flattenChildren), they both become
  		// an Array containing a single Stateful VNode. So when diff'ed they are compared together
  		// and the state of Stateful is preserved
  
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>
  					<Stateful key="a" />
  				</Fragment>
  			) : (
  				<Fragment>{[<Stateful key="a" />]}</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should preserve state between top level fragment and array', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				[<Stateful key="a" />]
  			) : (
  				<Fragment>
  					<Stateful key="a" />
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should not preserve state between array nested in fragment and double nested fragment', () => {
  		// In this test case, the children of the Fragment in Foo end up being the different when flattened.
  		//
  		// When condition == true, the children of the Fragment are an Array of Stateful VNode.
  		// When condition == false, the children of the Fragment are another Fragment whose children are
  		// a single Stateful VNode.
  		//
  		// When each of these are flattened (in flattenChildren), the first Fragment stays the same
  		// (Fragment -> [Stateful]). The second Fragment also doesn't change (flattening doesn't erase
  		// Fragments) so it remains Fragment -> Fragment -> Stateful. Therefore when diff'ed these Fragments
  		// separate the two Stateful VNodes into different trees and state is not preserved between them.
  
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>{[<Stateful key="a" />]}</Fragment>
  			) : (
  				<Fragment>
  					<Fragment>
  						<Stateful key="a" />
  					</Fragment>
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should not preserve state between array nested in fragment and double nested array', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>{[<Stateful key="a" />]}</Fragment>
  			) : (
  				[[<Stateful key="a" />]]
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it.skip('should preserve state between double nested fragment and double nested array', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment>
  					<Fragment>
  						<Stateful key="a" />
  					</Fragment>
  				</Fragment>
  			) : (
  				[[<Stateful key="a" />]]
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should not preserve state of children when the keys are different', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment key="a">
  					<Stateful />
  				</Fragment>
  			) : (
  				<Fragment key="b">
  					<Stateful />
  					<span>World</span>
  				</Fragment>
  			);
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div><span>World</span>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should not preserve state between unkeyed and keyed fragment', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<Fragment key="a">
  					<Stateful />
  				</Fragment>
  			) : (
  				<Fragment>
  					<Stateful />
  				</Fragment>
  			);
  		}
  
  		// React & Preact: has the same behavior for components
  		// https://codesandbox.io/s/57prmy5mx
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal('<div>Hello</div>');
  	});
  
  	it('should preserve state with reordering in multiple levels', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<div>
  					<Fragment key="c">
  						<div>foo</div>
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  					</Fragment>
  					<div>boop</div>
  				</div>
  			) : (
  				<div>
  					<div>beep</div>
  					<Fragment key="c">
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  						<div>bar</div>
  					</Fragment>
  				</div>
  			);
  		}
  
  		const htmlForTrue = div([div('foo'), div(div('Hello')), div('boop')]);
  
  		const htmlForFalse = div([div('beep'), div(div('Hello')), div('bar')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(scratch.innerHTML).to.equal(htmlForTrue);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal(htmlForFalse);
  		expectDomLogToBe(
  			[
  				'<div>fooHellobeep.insertBefore(<div>beep, <div>foo)',
  				'<div>beepbarHello.appendChild(<div>bar)'
  			],
  			'rendering true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal(htmlForTrue);
  		expectDomLogToBe(
  			[
  				'<div>beepHellofoo.insertBefore(<div>foo, <div>beep)',
  				'<div>fooboopHello.appendChild(<div>boop)'
  			],
  			'rendering false to true'
  		);
  	});
  
  	it('should not preserve state when switching to a keyed fragment to an array', () => {
  		function Foo({ condition }) {
  			return condition ? (
  				<div>
  					{
  						<Fragment key="foo">
  							<span>1</span>
  							<Stateful />
  						</Fragment>
  					}
  					<span>2</span>
  				</div>
  			) : (
  				<div>
  					{[<span>1</span>, <Stateful />]}
  					<span>2</span>
  				</div>
  			);
  		}
  
  		const html = div([span('1'), div('Hello'), span('2')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal(html);
  		expectDomLogToBe([
  			'<div>1Hello1.insertBefore(<span>1, <span>1)',
  			'<div>.appendChild(#text)',
  			'<div>11Hello.insertBefore(<div>Hello, <span>1)',
  			'<span>.appendChild(#text)',
  			'<div>1Hello1Hello.insertBefore(<span>2, <span>1)',
  			'<span>1.remove()',
  			'<div>Hello.remove()'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal([]);
  		expect(scratch.innerHTML).to.equal(html);
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>1Hello2.insertBefore(<span>1, <span>1)',
  			'<div>.appendChild(#text)',
  			'<div>11Hello2.insertBefore(<div>Hello, <span>1)',
  			'<span>2.remove()',
  			'<div>Hello.remove()'
  		]);
  	});
  
  	it('should preserve state when it does not change positions', () => {
  		function Foo({ condition }) {
  			return condition
  				? [
  						<span />,
  						<Fragment>
  							<Stateful />
  						</Fragment>
  				  ]
  				: [
  						<span />,
  						<Fragment>
  							<Stateful />
  						</Fragment>
  				  ];
  		}
  
  		render(<Foo condition={true} />, scratch);
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<span></span><div>Hello</div>');
  
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal('<span></span><div>Hello</div>');
  	});
  
  	it('should render nested Fragments', () => {
  		clearLog();
  		render(
  			<Fragment>
  				spam
  				<Fragment>foo</Fragment>
  				<Fragment />
  				bar
  			</Fragment>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.equal('spamfoobar');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>spam.appendChild(#text)',
  			'<div>spamfoo.appendChild(#text)'
  		]);
  
  		clearLog();
  		render(
  			<Fragment>
  				<Fragment>foo</Fragment>
  				<Fragment>bar</Fragment>
  			</Fragment>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.equal('foobar');
  		expectDomLogToBe([
  			'<div>spamfoobar.insertBefore(#text, #text)',
  			'#text.remove()',
  			'#text.remove()'
  		]);
  	});
  
  	it('should render nested Fragments with siblings', () => {
  		clearLog();
  		render(
  			<div>
  				<div>0</div>
  				<div>1</div>
  				<Fragment>
  					<Fragment>
  						<div>2</div>
  						<div>3</div>
  					</Fragment>
  				</Fragment>
  				<div>4</div>
  				<div>5</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.equal(
  			div([div(0), div(1), div(2), div(3), div(4), div(5)])
  		);
  	});
  
  	it('should respect keyed Fragments', () => {
  		/** @type {() => void} */
  		let update;
  
  		class Comp extends Component {
  			constructor() {
  				super();
  				this.state = { key: 'foo' };
  				update = () => this.setState({ key: 'bar' });
  			}
  
  			render() {
  				return <Fragment key={this.state.key}>foo</Fragment>;
  			}
  		}
  		render(<Comp />, scratch);
  		expect(scratch.innerHTML).to.equal('foo');
  
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.equal('foo');
  	});
  
  	it('should support conditionally rendered children', () => {
  		/** @type {() => void} */
  		let update;
  
  		class Comp extends Component {
  			constructor() {
  				super();
  				this.state = { value: true };
  				update = () => this.setState({ value: !this.state.value });
  			}
  
  			render() {
  				return (
  					<Fragment>
  						<span>0</span>
  						{this.state.value && 'foo'}
  						<span>1</span>
  					</Fragment>
  				);
  			}
  		}
  
  		const html = contents => span('0') + contents + span('1');
  
  		render(<Comp />, scratch);
  		expect(scratch.innerHTML).to.equal(html('foo'));
  
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.equal(html(''));
  
  		update();
  		rerender();
  		expect(scratch.innerHTML).to.equal(html('foo'));
  	});
  
  	it('can modify the children of a Fragment', () => {
  		/** @type {() => void} */
  		let push;
  
  		class List extends Component {
  			constructor() {
  				super();
  				this.state = { values: [0, 1, 2] };
  				push = () =>
  					this.setState({
  						values: [...this.state.values, this.state.values.length]
  					});
  			}
  
  			render() {
  				return (
  					<Fragment>
  						{this.state.values.map(value => (
  							<div>{value}</div>
  						))}
  					</Fragment>
  				);
  			}
  		}
  
  		render(<List />, scratch);
  		expect(scratch.textContent).to.equal('012');
  
  		push();
  		rerender();
  
  		expect(scratch.textContent).to.equal('0123');
  
  		push();
  		rerender();
  
  		expect(scratch.textContent).to.equal('01234');
  	});
  
  	it('should render sibling array children', () => {
  		const Group = ({ title, values }) => (
  			<Fragment>
  				<li>{title}</li>
  				{values.map(value => (
  					<li>{value}</li>
  				))}
  			</Fragment>
  		);
  
  		const Todo = () => (
  			<ul>
  				<Group title={'A header'} values={['a', 'b']} />
  				<Group title={'A divider'} values={['c', 'd']} />
  				<li>A footer</li>
  			</ul>
  		);
  
  		render(<Todo />, scratch);
  
  		expect(scratch.innerHTML).to.equal(
  			ul([
  				li('A header'),
  				li('a'),
  				li('b'),
  				li('A divider'),
  				li('c'),
  				li('d'),
  				li('A footer')
  			])
  		);
  	});
  
  	it('should reorder Fragment children', () => {
  		let updateState;
  
  		class App extends Component {
  			constructor() {
  				super();
  				this.state = { active: false };
  				updateState = () => this.setState(prev => ({ active: !prev.active }));
  			}
  
  			render() {
  				return (
  					<div>
  						<h1>Heading</h1>
  						{!this.state.active ? (
  							<Fragment>
  								foobar
  								<Fragment>
  									Hello World
  									<h2>yo</h2>
  								</Fragment>
  								<input type="text" />
  							</Fragment>
  						) : (
  							<Fragment>
  								<Fragment>
  									Hello World
  									<h2>yo</h2>
  								</Fragment>
  								foobar
  								<input type="text" />
  							</Fragment>
  						)}
  					</div>
  				);
  			}
  		}
  
  		render(<App />, scratch);
  
  		expect(scratch.innerHTML).to.equal(
  			'<div><h1>Heading</h1>foobarHello World<h2>yo</h2><input type="text"></div>'
  		);
  
  		updateState();
  
  		// See "should preserve state between top level fragment and array"
  		// Perhaps rename test to "should reorder **keyed** Fragment children"
  		rerender();
  		expect(scratch.innerHTML).to.equal(
  			'<div><h1>Heading</h1>Hello World<h2>yo</h2>foobar<input type="text"></div>'
  		);
  	});
  
  	it('should render sibling fragments with multiple children in the correct order', () => {
  		render(
  			<ol>
  				<li>0</li>
  				<Fragment>
  					<li>1</li>
  					<li>2</li>
  				</Fragment>
  				<li>3</li>
  				<li>4</li>
  				<Fragment>
  					<li>5</li>
  					<li>6</li>
  				</Fragment>
  				<li>7</li>
  			</ol>,
  			scratch
  		);
  
  		expect(scratch.textContent).to.equal('01234567');
  	});
  
  	it('should support HOCs that return children', () => {
  		const text =
  			"Don't forget to tell these special people in your life just how special they are to you.";
  
  		class BobRossProvider extends Component {
  			getChildContext() {
  				return { text };
  			}
  
  			render(props) {
  				return props.children;
  			}
  		}
  
  		function BobRossConsumer(props, context) {
  			return props.children(context.text);
  		}
  
  		const Say = props => <div>{props.text}</div>;
  
  		const Speak = () => (
  			<Fragment>
  				<span>the top</span>
  				<BobRossProvider>
  					<span>a span</span>
  					<BobRossConsumer>
  						{text => [<Say text={text} />, <Say text={text} />]}
  					</BobRossConsumer>
  					<span>another span</span>
  				</BobRossProvider>
  				<span>a final span</span>
  			</Fragment>
  		);
  
  		render(<Speak />, scratch);
  
  		expect(scratch.innerHTML).to.equal(
  			[
  				span('the top'),
  				span('a span'),
  				div(text),
  				div(text),
  				span('another span'),
  				span('a final span')
  			].join('')
  		);
  	});
  
  	it('should support conditionally rendered Fragment', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				<li>0</li>
  				{condition ? (
  					<Fragment>
  						<li>1</li>
  						<li>2</li>
  					</Fragment>
  				) : (
  					[<li>1</li>, <li>2</li>]
  				)}
  				<li>3</li>
  			</ol>
  		);
  
  		const html = ol([li('0'), li('1'), li('2'), li('3')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(html, 'initial render of true');
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>.appendChild(<li>0)',
  				'<li>.appendChild(#text)',
  				'<ol>0.appendChild(<li>1)',
  				'<li>.appendChild(#text)',
  				'<ol>01.appendChild(<li>2)',
  				'<li>.appendChild(#text)',
  				'<ol>012.appendChild(<li>3)',
  				'<div>.appendChild(<ol>0123)'
  			],
  			'initial render of true'
  		);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(html, 'rendering from true to false');
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>0121.appendChild(<li>2)',
  				'<li>.appendChild(#text)',
  				'<ol>01212.appendChild(<li>3)',
  				'<li>1.remove()',
  				'<li>2.remove()'
  			],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(html, 'rendering from false to true');
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>0123.insertBefore(<li>1, <li>1)',
  				'<li>.appendChild(#text)',
  				'<ol>01123.insertBefore(<li>2, <li>1)',
  				'<li>3.remove()',
  				'<li>1.remove()'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should support conditionally rendered Fragment or null', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				<li>0</li>
  				{condition ? (
  					<Fragment>
  						<li>1</li>
  						<li>2</li>
  					</Fragment>
  				) : null}
  				<li>3</li>
  				<li>4</li>
  			</ol>
  		);
  
  		const htmlForTrue = ol([li('0'), li('1'), li('2'), li('3'), li('4')]);
  
  		const htmlForFalse = ol([li('0'), li('3'), li('4')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(htmlForTrue, 'initial render of true');
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>.appendChild(<li>0)',
  				'<li>.appendChild(#text)',
  				'<ol>0.appendChild(<li>1)',
  				'<li>.appendChild(#text)',
  				'<ol>01.appendChild(<li>2)',
  				'<li>.appendChild(#text)',
  				'<ol>012.appendChild(<li>3)',
  				'<li>.appendChild(#text)',
  				'<ol>0123.appendChild(<li>4)',
  				'<div>.appendChild(<ol>01234)'
  			],
  			'initial render of true'
  		);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe(
  			['<li>1.remove()', '<li>2.remove()'],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>034.insertBefore(<li>1, <li>3)',
  				'<li>.appendChild(#text)',
  				'<ol>0134.insertBefore(<li>2, <li>3)'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should support moving Fragments between beginning and end', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				{condition
  					? [
  							<li>0</li>,
  							<li>1</li>,
  							<li>2</li>,
  							<li>3</li>,
  							<Fragment>
  								<li>4</li>
  								<li>5</li>
  							</Fragment>
  					  ]
  					: [
  							<Fragment>
  								<li>4</li>
  								<li>5</li>
  							</Fragment>,
  							<li>0</li>,
  							<li>1</li>,
  							<li>2</li>,
  							<li>3</li>
  					  ]}
  			</ol>
  		);
  
  		const htmlForTrue = ol([
  			li('0'),
  			li('1'),
  			li('2'),
  			li('3'),
  			li('4'),
  			li('5')
  		]);
  
  		const htmlForFalse = ol([
  			li('4'),
  			li('5'),
  			li('0'),
  			li('1'),
  			li('2'),
  			li('3')
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(htmlForTrue, 'initial render of true');
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe([
  			'<ol>012345.insertBefore(<li>4, <li>0)',
  			'<ol>401235.insertBefore(<li>5, <li>0)',
  			// TODO: Hmmm why does this extra append happen?
  			'<ol>453012.appendChild(<li>3)'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe([
  			'<ol>450123.appendChild(<li>4)',
  			'<ol>501234.appendChild(<li>5)'
  		]);
  	});
  
  	it('should support conditional beginning and end Fragments', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				{condition ? (
  					<Fragment>
  						<li>0</li>
  						<li>1</li>
  					</Fragment>
  				) : null}
  				<li>2</li>
  				<li>2</li>
  				{condition ? null : (
  					<Fragment>
  						<li>3</li>
  						<li>4</li>
  					</Fragment>
  				)}
  			</ol>
  		);
  
  		const htmlForTrue = ol([li(0), li(1), li(2), li(2)]);
  
  		const htmlForFalse = ol([li(2), li(2), li(3), li(4)]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(htmlForTrue, 'initial render of true');
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe([
  			// Mount 3 & 4
  			'<li>.appendChild(#text)',
  			'<ol>0122.appendChild(<li>3)',
  			'<li>.appendChild(#text)',
  			'<ol>01223.appendChild(<li>4)',
  			// Remove 1 & 2 (replaced with null)
  			'<li>0.remove()',
  			'<li>1.remove()'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe([
  			// Insert 0 and 1
  			'<li>.appendChild(#text)',
  			'<ol>2234.insertBefore(<li>0, <li>2)',
  			'<li>.appendChild(#text)',
  			'<ol>02234.insertBefore(<li>1, <li>2)',
  			// Remove 3 & 4 (replaced by null)
  			'<li>3.remove()',
  			'<li>4.remove()'
  		]);
  	});
  
  	it('should support nested conditional beginning and end Fragments', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				{condition ? (
  					<Fragment>
  						<Fragment>
  							<Fragment>
  								<li>0</li>
  								<li>1</li>
  							</Fragment>
  						</Fragment>
  					</Fragment>
  				) : null}
  				<li>2</li>
  				<li>3</li>
  				{condition ? null : (
  					<Fragment>
  						<Fragment>
  							<Fragment>
  								<li>4</li>
  								<li>5</li>
  							</Fragment>
  						</Fragment>
  					</Fragment>
  				)}
  			</ol>
  		);
  
  		const htmlForTrue = ol([li(0), li(1), li(2), li(3)]);
  
  		const htmlForFalse = ol([li(2), li(3), li(4), li(5)]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(htmlForTrue, 'initial render of true');
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe([
  			// Mount 4 & 5
  			'<li>.appendChild(#text)',
  			'<ol>0123.appendChild(<li>4)',
  			'<li>.appendChild(#text)',
  			'<ol>01234.appendChild(<li>5)',
  			// Remove 1 & 2 (replaced with null)
  			'<li>0.remove()',
  			'<li>1.remove()'
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe([
  			// Insert 0 and 1 back into the DOM
  			'<li>.appendChild(#text)',
  			'<ol>2345.insertBefore(<li>0, <li>2)',
  			'<li>.appendChild(#text)',
  			'<ol>02345.insertBefore(<li>1, <li>2)',
  			// Remove 4 & 5 (replaced by null)
  			'<li>4.remove()',
  			'<li>5.remove()'
  		]);
  	});
  
  	it('should preserve state with reordering in multiple levels with mixed # of Fragment siblings', () => {
  		// Also fails if the # of divs outside the Fragment equals or exceeds
  		// the # inside the Fragment for both conditions
  		function Foo({ condition }) {
  			return condition ? (
  				<div>
  					<Fragment key="c">
  						<div>foo</div>
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  					</Fragment>
  					<div>boop</div>
  					<div>boop</div>
  				</div>
  			) : (
  				<div>
  					<div>beep</div>
  					<Fragment key="c">
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  						<div>bar</div>
  					</Fragment>
  				</div>
  			);
  		}
  
  		const htmlForTrue = div([
  			div('foo'),
  			div(div('Hello')),
  			div('boop'),
  			div('boop')
  		]);
  
  		const htmlForFalse = div([div('beep'), div(div('Hello')), div('bar')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe(
  			[
  				'<div>fooHellobeepboop.insertBefore(<div>Hello, <div>boop)',
  				'<div>barbeepHelloboop.insertBefore(<div>bar, <div>boop)',
  				'<div>boop.remove()'
  			],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe(
  			[
  				'<div>beepHellofoo.insertBefore(<div>foo, <div>beep)',
  				'<div>fooboopHello.appendChild(<div>boop)',
  				'<div>.appendChild(#text)',
  				'<div>fooHelloboop.appendChild(<div>boop)'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should preserve state with reordering in multiple levels with lots of Fragment siblings', () => {
  		// Also fails if the # of divs outside the Fragment equals or exceeds
  		// the # inside the Fragment for both conditions
  		function Foo({ condition }) {
  			return condition ? (
  				<div>
  					<Fragment key="c">
  						<div>foo</div>
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  					</Fragment>
  					<div>boop</div>
  					<div>boop</div>
  					<div>boop</div>
  				</div>
  			) : (
  				<div>
  					<div>beep</div>
  					<div>beep</div>
  					<div>beep</div>
  					<Fragment key="c">
  						<div key="b">
  							<Stateful key="a" />
  						</div>
  						<div>bar</div>
  					</Fragment>
  				</div>
  			);
  		}
  
  		const htmlForTrue = div([
  			div('foo'),
  			div(div('Hello')),
  			div('boop'),
  			div('boop'),
  			div('boop')
  		]);
  
  		const htmlForFalse = div([
  			div('beep'),
  			div('beep'),
  			div('beep'),
  			div(div('Hello')),
  			div('bar')
  		]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful']);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe(
  			[
  				'<div>fooHellobeepbeepbeep.appendChild(<div>Hello)',
  				'<div>barbeepbeepbeepHello.appendChild(<div>bar)'
  			],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(ops).to.deep.equal(['Update Stateful', 'Update Stateful']);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe(
  			[
  				'<div>beepbeepbeepHellofoo.insertBefore(<div>foo, <div>beep)',
  				'<div>foobeepbeepbeepHello.insertBefore(<div>Hello, <div>beep)',
  				'<div>fooHelloboopboopboop.appendChild(<div>boop)'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should correctly append children with siblings', () => {
  		/**
  		 * @type {(props: { values: Array<string | number>}) => JSX.Element}
  		 */
  		const Foo = ({ values }) => (
  			<ol>
  				<li>a</li>
  				<Fragment>
  					{values.map(value => (
  						<li>{value}</li>
  					))}
  				</Fragment>
  				<li>b</li>
  			</ol>
  		);
  
  		const getHtml = values =>
  			ol([li('a'), ...values.map(value => li(value)), li('b')]);
  
  		let values = [0, 1, 2];
  		clearLog();
  		render(<Foo values={values} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			getHtml(values),
  			`original list: [${values.join(',')}]`
  		);
  
  		values.push(3);
  
  		clearLog();
  		render(<Foo values={values} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			getHtml(values),
  			`push 3: [${values.join(',')}]`
  		);
  		expectDomLogToBe([
  			'<li>.appendChild(#text)',
  			'<ol>a012b.insertBefore(<li>3, <li>b)'
  		]);
  
  		values.push(4);
  
  		clearLog();
  		render(<Foo values={values} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			getHtml(values),
  			`push 4: [${values.join(',')}]`
  		);
  		expectDomLogToBe([
  			'<li>.appendChild(#text)',
  			'<ol>a0123b.insertBefore(<li>4, <li>b)'
  		]);
  	});
  
  	it('should render components that conditionally return Fragments', () => {
  		const Foo = ({ condition }) =>
  			condition ? (
  				<Fragment>
  					<div>1</div>
  					<div>2</div>
  				</Fragment>
  			) : (
  				<div>
  					<div>3</div>
  					<div>4</div>
  				</div>
  			);
  
  		const htmlForTrue = [div(1), div(2)].join('');
  
  		const htmlForFalse = div([div(3), div(4)]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(scratch.innerHTML).to.equal(htmlForTrue);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  
  		expect(scratch.innerHTML).to.equal(htmlForFalse);
  		expectDomLogToBe(
  			[
  				'<div>.appendChild(#text)',
  				'<div>1.insertBefore(<div>3, #text)',
  				'<div>.appendChild(#text)',
  				'<div>31.insertBefore(<div>4, #text)',
  				'#text.remove()',
  				'<div>2.remove()'
  			],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  
  		expect(scratch.innerHTML).to.equal(htmlForTrue);
  		expectDomLogToBe(
  			[
  				'<div>34.insertBefore(#text, <div>3)',
  				'<div>4.remove()',
  				'<div>3.remove()',
  				'<div>.appendChild(#text)',
  				'<div>1.appendChild(<div>2)'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should clear empty Fragments', () => {
  		function Foo(props) {
  			if (props.condition) {
  				return <Fragment>foo</Fragment>;
  			}
  			return <Fragment />;
  		}
  
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.textContent).to.equal('foo');
  
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.textContent).to.equal('');
  	});
  
  	it('should support conditionally rendered nested Fragments or null with siblings', () => {
  		const Foo = ({ condition }) => (
  			<ol>
  				<li>0</li>
  				<Fragment>
  					<li>1</li>
  					{condition ? (
  						<Fragment>
  							<li>2</li>
  							<li>3</li>
  						</Fragment>
  					) : null}
  					<li>4</li>
  				</Fragment>
  				<li>5</li>
  			</ol>
  		);
  
  		const htmlForTrue = ol([
  			li('0'),
  			li('1'),
  			li('2'),
  			li('3'),
  			li('4'),
  			li('5')
  		]);
  
  		const htmlForFalse = ol([li('0'), li('1'), li('4'), li('5')]);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(htmlForTrue, 'initial render of true');
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>.appendChild(<li>0)',
  				'<li>.appendChild(#text)',
  				'<ol>0.appendChild(<li>1)',
  				'<li>.appendChild(#text)',
  				'<ol>01.appendChild(<li>2)',
  				'<li>.appendChild(#text)',
  				'<ol>012.appendChild(<li>3)',
  				'<li>.appendChild(#text)',
  				'<ol>0123.appendChild(<li>4)',
  				'<li>.appendChild(#text)',
  				'<ol>01234.appendChild(<li>5)',
  				'<div>.appendChild(<ol>012345)'
  			],
  			'initial render of true'
  		);
  
  		clearLog();
  		render(<Foo condition={false} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForFalse,
  			'rendering from true to false'
  		);
  		expectDomLogToBe(
  			['<li>2.remove()', '<li>3.remove()'],
  			'rendering from true to false'
  		);
  
  		clearLog();
  		render(<Foo condition={true} />, scratch);
  		expect(scratch.innerHTML).to.equal(
  			htmlForTrue,
  			'rendering from false to true'
  		);
  		expectDomLogToBe(
  			[
  				'<li>.appendChild(#text)',
  				'<ol>0145.insertBefore(<li>2, <li>4)',
  				'<li>.appendChild(#text)',
  				'<ol>01245.insertBefore(<li>3, <li>4)'
  			],
  			'rendering from false to true'
  		);
  	});
  
  	it('should render first child Fragment that wrap null components', () => {
  		const Empty = () => null;
  		const Foo = () => (
  			<ol>
  				<Fragment>
  					<Empty />
  				</Fragment>
  				<li>1</li>
  			</ol>
  		);
  
  		render(<Foo />, scratch);
  		expect(scratch.innerHTML).to.equal(ol(li(1)));
  	});
  
  	it('should properly render Components that return Fragments and use shouldComponentUpdate #1415', () => {
  		class SubList extends Component {
  			shouldComponentUpdate(nextProps) {
  				return nextProps.prop1 !== this.props.prop1;
  			}
  			render() {
  				return (
  					<Fragment>
  						<div>2</div>
  						<div>3</div>
  					</Fragment>
  				);
  			}
  		}
  
  		/** @type {(update: any) => void} */
  		let setState;
  		class App extends Component {
  			constructor() {
  				super();
  				setState = update => this.setState(update);
  
  				this.state = { error: false };
  			}
  
  			render() {
  				return (
  					<div>
  						{this.state.error ? (
  							<div>Error!</div>
  						) : (
  							<div>
  								<div>1</div>
  								<SubList prop1={this.state.error} />
  							</div>
  						)}
  					</div>
  				);
  			}
  		}
  
  		const successHtml = div(div([div(1), div(2), div(3)]));
  
  		const errorHtml = div(div('Error!'));
  
  		render(<App />, scratch);
  		expect(scratch.innerHTML).to.equal(successHtml);
  
  		setState({}); // Trigger sCU
  		rerender();
  		expect(scratch.innerHTML).to.equal(successHtml);
  
  		setState({ error: true });
  		rerender();
  		expect(scratch.innerHTML).to.equal(errorHtml);
  
  		setState({ error: false });
  		rerender();
  		expect(scratch.innerHTML).to.equal(successHtml);
  
  		setState({}); // Trigger sCU again
  		rerender();
  		expect(scratch.innerHTML).to.equal(successHtml);
  	});
  
  	it('should properly render Fragments whose last child is a component returning null', () => {
  		let Noop = () => null;
  		let update;
  		class App extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ items: ['A', 'B', 'C'] });
  				this.state = {
  					items: null
  				};
  			}
  
  			render() {
  				return (
  					<div>
  						{this.state.items && (
  							<Fragment>
  								{this.state.items.map(v => (
  									<div>{v}</div>
  								))}
  								<Noop />
  							</Fragment>
  						)}
  					</div>
  				);
  			}
  		}
  
  		render(<App />, scratch);
  		expect(scratch.textContent).to.equal('');
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.textContent).to.equal('ABC');
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>.appendChild(<div>A)',
  			'<div>.appendChild(#text)',
  			'<div>A.appendChild(<div>B)',
  			'<div>.appendChild(#text)',
  			'<div>AB.appendChild(<div>C)'
  		]);
  	});
  
  	it('should replace node in-between children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? <section>B2</section> : <div>B1</div>;
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				<SetState />
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B1</div><div>C</div></div>`
  		);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><section>B2</section><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<section>.appendChild(#text)',
  			'<div>AB1C.insertBefore(<section>B2, <div>B1)',
  			'<div>B1.remove()'
  		]);
  	});
  
  	it('should replace Fragment in-between children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? (
  					<Fragment>
  						<section>B3</section>
  						<section>B4</section>
  					</Fragment>
  				) : (
  					<Fragment>
  						<div>B1</div>
  						<div>B2</div>
  					</Fragment>
  				);
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				<SetState />
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(
  			div([div('A'), div('B1'), div('B2'), div('C')])
  		);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			div([div('A'), section('B3'), section('B4'), div('C')])
  		);
  		expectDomLogToBe([
  			'<section>.appendChild(#text)',
  			'<div>AB1B2C.insertBefore(<section>B3, <div>B1)',
  			'<section>.appendChild(#text)',
  			'<div>AB3B1B2C.insertBefore(<section>B4, <div>B1)',
  			'<div>B2.remove()',
  			'<div>B1.remove()'
  		]);
  	});
  
  	it('should insert in-between children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? <div>B</div> : null;
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				<SetState />
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B, <div>C)'
  		]);
  	});
  
  	it('should insert in-between Fragments', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? [<div>B1</div>, <div>B2</div>] : null;
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				<SetState />
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B1</div><div>B2</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B1, <div>C)',
  			'<div>.appendChild(#text)',
  			'<div>AB1C.insertBefore(<div>B2, <div>C)'
  		]);
  	});
  
  	it('should insert in-between null children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? <div>B</div> : null;
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				{null}
  				<SetState />
  				{null}
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B, <div>C)'
  		]);
  	});
  
  	it('should insert Fragment in-between null children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? (
  					<Fragment>
  						<div>B1</div>
  						<div>B2</div>
  					</Fragment>
  				) : null;
  			}
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				{null}
  				<SetState />
  				{null}
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B1</div><div>B2</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B1, <div>C)',
  			'<div>.appendChild(#text)',
  			'<div>AB1C.insertBefore(<div>B2, <div>C)'
  		]);
  	});
  
  	it('should insert in-between nested null children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? <div>B</div> : null;
  			}
  		}
  
  		function Outer() {
  			return <SetState />;
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				{null}
  				<Outer />
  				{null}
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B, <div>C)'
  		]);
  	});
  
  	it('should insert Fragment in-between nested null children', () => {
  		let update;
  		class SetState extends Component {
  			constructor(props) {
  				super(props);
  				update = () => this.setState({ active: true });
  			}
  
  			render() {
  				return this.state.active ? (
  					<Fragment>
  						<div>B1</div>
  						<div>B2</div>
  					</Fragment>
  				) : null;
  			}
  		}
  
  		function Outer() {
  			return <SetState />;
  		}
  
  		render(
  			<div>
  				<div>A</div>
  				{null}
  				<Outer />
  				{null}
  				<div>C</div>
  			</div>,
  			scratch
  		);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  
  		clearLog();
  		update();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B1</div><div>B2</div><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B1, <div>C)',
  			'<div>.appendChild(#text)',
  			'<div>AB1C.insertBefore(<div>B2, <div>C)'
  		]);
  	});
  
  	it('should update at correct place', () => {
  		let updateA;
  		class A extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: true };
  				updateA = () => this.setState(prev => ({ active: !prev.active }));
  			}
  
  			render() {
  				return this.state.active ? <div>A</div> : <span>A2</span>;
  			}
  		}
  
  		function B() {
  			return <div>B</div>;
  		}
  
  		function X(props) {
  			return props.children;
  		}
  
  		function App(props) {
  			let b = props.condition ? <B /> : null;
  			return (
  				<div>
  					<X>
  						<A />
  					</X>
  					<X>
  						{b}
  						<div>C</div>
  					</X>
  				</div>
  			);
  		}
  
  		render(<App condition={true} />, scratch);
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A</div><div>B</div><div>C</div></div>`
  		);
  
  		clearLog();
  		render(<App condition={false} />, scratch);
  
  		expect(scratch.innerHTML).to.eql(`<div><div>A</div><div>C</div></div>`);
  		expectDomLogToBe(['<div>B.remove()']);
  
  		clearLog();
  		updateA();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(`<div><span>A2</span><div>C</div></div>`);
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>AC.insertBefore(<span>A2, <div>A)',
  			'<div>A.remove()'
  		]);
  	});
  
  	it('should update Fragment at correct place', () => {
  		let updateA;
  		class A extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: true };
  				updateA = () => this.setState(prev => ({ active: !prev.active }));
  			}
  
  			render() {
  				return this.state.active
  					? [<div>A1</div>, <div>A2</div>]
  					: [<span>A3</span>, <span>A4</span>];
  			}
  		}
  
  		function B() {
  			return <div>B</div>;
  		}
  
  		function X(props) {
  			return props.children;
  		}
  
  		function App(props) {
  			let b = props.condition ? <B /> : null;
  			return (
  				<div>
  					<X>
  						<A />
  					</X>
  					<X>
  						{b}
  						<div>C</div>
  					</X>
  				</div>
  			);
  		}
  
  		render(<App condition={true} />, scratch);
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A1</div><div>A2</div><div>B</div><div>C</div></div>`
  		);
  
  		clearLog();
  		render(<App condition={false} />, scratch);
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><div>A1</div><div>A2</div><div>C</div></div>`
  		);
  		expectDomLogToBe(['<div>B.remove()']);
  
  		clearLog();
  		updateA();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			`<div><span>A3</span><span>A4</span><div>C</div></div>`
  		);
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>A1A2C.insertBefore(<span>A3, <div>A1)',
  			'<span>.appendChild(#text)',
  			'<div>A3A1A2C.insertBefore(<span>A4, <div>A1)',
  			'<div>A2.remove()',
  			'<div>A1.remove()'
  		]);
  	});
  
  	it('should insert children correctly if sibling component DOM changes', () => {
  		/** @type {() => void} */
  		let updateA;
  		class A extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: true };
  				updateA = () => this.setState(prev => ({ active: !prev.active }));
  			}
  
  			render() {
  				return this.state.active ? <div>A</div> : <span>A2</span>;
  			}
  		}
  
  		/** @type {() => void} */
  		let updateB;
  		class B extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: false };
  				updateB = () => this.setState(prev => ({ active: !prev.active }));
  			}
  			render() {
  				return this.state.active ? <div>B</div> : null;
  			}
  		}
  
  		function X(props) {
  			return props.children;
  		}
  
  		function App() {
  			return (
  				<div>
  					<X>
  						<A />
  					</X>
  					<X>
  						<B />
  						<div>C</div>
  					</X>
  				</div>
  			);
  		}
  
  		render(<App />, scratch);
  
  		expect(scratch.innerHTML).to.eql(div([div('A'), div('C')]), 'initial');
  
  		clearLog();
  		updateB();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			div([div('A'), div('B'), div('C')]),
  			'updateB'
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>AC.insertBefore(<div>B, <div>C)'
  		]);
  
  		clearLog();
  		updateA();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			div([span('A2'), div('B'), div('C')]),
  			'updateA'
  		);
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>ABC.insertBefore(<span>A2, <div>A)',
  			'<div>A.remove()'
  		]);
  	});
  
  	it('should correctly append children if last child changes DOM', () => {
  		/** @type {() => void} */
  		let updateA;
  		class A extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: true };
  				updateA = () => this.setState(prev => ({ active: !prev.active }));
  			}
  
  			render() {
  				return this.state.active
  					? [<div>A1</div>, <div>A2</div>]
  					: [<span>A3</span>, <span>A4</span>];
  			}
  		}
  
  		/** @type {() => void} */
  		let updateB;
  		class B extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { active: false };
  				updateB = () => this.setState(prev => ({ active: !prev.active }));
  			}
  			render() {
  				return (
  					<Fragment>
  						<A />
  						{this.state.active ? <div>B</div> : null}
  					</Fragment>
  				);
  			}
  		}
  
  		render(<B />, scratch);
  
  		expect(scratch.innerHTML).to.eql(
  			[div('A1'), div('A2')].join(''),
  			'initial'
  		);
  
  		clearLog();
  		updateA();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			[span('A3'), span('A4')].join(''),
  			'updateA'
  		);
  		expectDomLogToBe([
  			'<span>.appendChild(#text)',
  			'<div>A1A2.insertBefore(<span>A3, <div>A1)',
  			'<span>.appendChild(#text)',
  			'<div>A3A1A2.insertBefore(<span>A4, <div>A1)',
  			'<div>A2.remove()',
  			'<div>A1.remove()'
  		]);
  
  		clearLog();
  		updateB();
  		rerender();
  
  		expect(scratch.innerHTML).to.eql(
  			[span('A3'), span('A4'), div('B')].join(''),
  			'updateB'
  		);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>A3A4.appendChild(<div>B)'
  		]);
  	});
  
  	it('should properly place conditional elements around strictly equal vnodes', () => {
  		expectDomLog = true;
  		let set;
  
  		const Children = () => (
  			<Fragment>
  				<div>Navigation</div>
  				<div>Content</div>
  			</Fragment>
  		);
  
  		class Parent extends Component {
  			constructor(props) {
  				super(props);
  				this.state = { panelPosition: 'bottom' };
  				set = this.tooglePanelPosition = this.tooglePanelPosition.bind(this);
  			}
  
  			tooglePanelPosition() {
  				this.setState({
  					panelPosition: this.state.panelPosition === 'top' ? 'bottom' : 'top'
  				});
  			}
  
  			render() {
  				return (
  					<div>
  						{this.state.panelPosition === 'top' && <div>top panel</div>}
  						{this.props.children}
  						{this.state.panelPosition === 'bottom' && <div>bottom panel</div>}
  					</div>
  				);
  			}
  		}
  
  		const App = () => (
  			<Parent>
  				<Children />
  			</Parent>
  		);
  
  		const content = `<div>Navigation</div><div>Content</div>`;
  		const top = `<div><div>top panel</div>${content}</div>`;
  		const bottom = `<div>${content}<div>bottom panel</div></div>`;
  
  		render(<App />, scratch);
  		expect(scratch.innerHTML).to.equal(bottom);
  
  		clearLog();
  		set();
  		rerender();
  		expect(scratch.innerHTML).to.equal(top);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>NavigationContentbottom panel.insertBefore(<div>top panel, <div>Navigation)',
  			'<div>bottom panel.remove()'
  		]);
  
  		clearLog();
  		set();
  		rerender();
  		expect(scratch.innerHTML).to.equal(bottom);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>top panelNavigationContent.appendChild(<div>bottom panel)',
  			'<div>top panel.remove()'
  		]);
  
  		clearLog();
  		set();
  		rerender();
  		expect(scratch.innerHTML).to.equal(top);
  		expectDomLogToBe([
  			'<div>.appendChild(#text)',
  			'<div>NavigationContentbottom panel.insertBefore(<div>top panel, <div>Navigation)',
  			'<div>bottom panel.remove()'
  		]);
  
  		expectDomLog = false;
  	});
  });
  