import { createElement, render, Component } from 'preact'; import { setupScratch, teardown, serializeHtml, sortAttributes } from '../_util/helpers'; /** @jsx createElement */ describe('replaceNode parameter in render()', () => { let scratch; /** * @param {HTMLDivElement} container * @returns {HTMLDivElement[]} */ function setupABCDom(container) { return ['a', 'b', 'c'].map(id => { const child = document.createElement('div'); child.id = id; container.appendChild(child); return child; }); } beforeEach(() => { scratch = setupScratch(); }); afterEach(() => { teardown(scratch); }); it('should use replaceNode as render root and not inject into it', () => { setupABCDom(scratch); const childA = scratch.querySelector('#a'); render(
contents
, scratch, childA); expect(scratch.querySelector('#a')).to.equalNode(childA); expect(childA.innerHTML).to.equal('contents'); }); it('should not remove siblings of replaceNode', () => { setupABCDom(scratch); const childA = scratch.querySelector('#a'); render(
, scratch, childA); expect(scratch.innerHTML).to.equal( '
' ); }); it('should notice prop changes on replaceNode', () => { setupABCDom(scratch); const childA = scratch.querySelector('#a'); render(
, scratch, childA); expect(sortAttributes(String(scratch.innerHTML))).to.equal( sortAttributes( '
' ) ); }); it('should unmount existing components', () => { const unmount = sinon.spy(); const mount = sinon.spy(); class App extends Component { componentDidMount() { mount(); } componentWillUnmount() { unmount(); } render() { return
App
; } } render(
, scratch ); expect(scratch.innerHTML).to.equal('
App
'); expect(mount).to.be.calledOnce; render(
new
, scratch, scratch.querySelector('#a')); expect(scratch.innerHTML).to.equal('
new
'); expect(unmount).to.be.calledOnce; }); it('should unmount existing components in prerendered HTML', () => { const unmount = sinon.spy(); const mount = sinon.spy(); class App extends Component { componentDidMount() { mount(); } componentWillUnmount() { unmount(); } render() { return App; } } scratch.innerHTML = `
`; const childContainer = scratch.querySelector('#child'); render(, childContainer); expect(serializeHtml(childContainer)).to.equal('App'); expect(mount).to.be.calledOnce; render(
, scratch, scratch.firstElementChild); expect(serializeHtml(scratch)).to.equal('
'); expect(unmount).to.be.calledOnce; }); it('should render multiple render roots in one parentDom', () => { setupABCDom(scratch); const childA = scratch.querySelector('#a'); const childB = scratch.querySelector('#b'); const childC = scratch.querySelector('#c'); const expectedA = '
childA
'; const expectedB = '
childB
'; const expectedC = '
childC
'; render(
childA
, scratch, childA); render(
childB
, scratch, childB); render(
childC
, scratch, childC); expect(scratch.innerHTML).to.equal(`${expectedA}${expectedB}${expectedC}`); }); it('should call unmount when working with replaceNode', () => { const mountSpy = sinon.spy(); const unmountSpy = sinon.spy(); class MyComponent extends Component { componentDidMount() { mountSpy(); } componentWillUnmount() { unmountSpy(); } render() { return
My Component
; } } const container = document.createElement('div'); scratch.appendChild(container); render(, scratch, container); expect(mountSpy).to.be.calledOnce; render(
Not my component
, document.body, container); expect(unmountSpy).to.be.calledOnce; }); it('should double replace', () => { const container = document.createElement('div'); scratch.appendChild(container); render(
Hello
, scratch, scratch.firstElementChild); expect(scratch.innerHTML).to.equal('
Hello
'); render(
Hello
, scratch, scratch.firstElementChild); expect(scratch.innerHTML).to.equal('
Hello
'); }); it('should replaceNode after rendering', () => { function App({ i }) { return

{i}

; } render(, scratch); expect(scratch.innerHTML).to.equal('

2

'); render(, scratch, scratch.firstChild); expect(scratch.innerHTML).to.equal('

3

'); }); it("shouldn't remove elements on subsequent renders with replaceNode", () => { const placeholder = document.createElement('div'); scratch.appendChild(placeholder); const App = () => (
New content
); render(, scratch, placeholder); expect(scratch.innerHTML).to.equal( '
New content
' ); render(, scratch, placeholder); expect(scratch.innerHTML).to.equal( '
New content
' ); }); it('should remove redundant elements on subsequent renders with replaceNode', () => { const placeholder = document.createElement('div'); scratch.appendChild(placeholder); const App = () => (
New content
); render(, scratch, placeholder); expect(scratch.innerHTML).to.equal( '
New content
' ); placeholder.appendChild(document.createElement('span')); expect(scratch.innerHTML).to.equal( '
New content
' ); render(, scratch, placeholder); expect(scratch.innerHTML).to.equal( '
New content
' ); }); });