From 38acabfa6089ab8ac469c12b5f55022fb96935e5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 23 Aug 2021 16:46:06 -0300 Subject: added web vendors --- preact/test/browser/placeholders.test.js | 308 +++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 preact/test/browser/placeholders.test.js (limited to 'preact/test/browser/placeholders.test.js') diff --git a/preact/test/browser/placeholders.test.js b/preact/test/browser/placeholders.test.js new file mode 100644 index 0000000..5b52ee6 --- /dev/null +++ b/preact/test/browser/placeholders.test.js @@ -0,0 +1,308 @@ +import { createElement, Component, render, createRef } from 'preact'; +import { setupRerender } from 'preact/test-utils'; +import { setupScratch, teardown } from '../_util/helpers'; +import { logCall, clearLog, getLog } from '../_util/logCall'; +import { div } from '../_util/dom'; + +/** @jsx createElement */ + +describe('null placeholders', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + /** @type {string[]} */ + let ops; + + function createNullable(name) { + return function Nullable(props) { + return props.show ? name : null; + }; + } + + /** + * @param {string} name + * @returns {[import('preact').ComponentClass, import('preact').RefObject<{ toggle(): void }>]} + */ + function createStatefulNullable(name) { + let ref = createRef(); + class Nullable extends Component { + constructor(props) { + super(props); + this.state = { show: props.initialShow || true }; + ref.current = this; + } + toggle() { + this.setState({ show: !this.state.show }); + } + componentDidUpdate() { + ops.push(`Update ${name}`); + } + componentDidMount() { + ops.push(`Mount ${name}`); + } + componentWillUnmount() { + ops.push(`Unmount ${name}`); + } + render() { + return this.state.show ?
{name}
: null; + } + } + + return [Nullable, ref]; + } + + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + + before(() => { + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); + }); + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + ops = []; + }); + + afterEach(() => { + teardown(scratch); + clearLog(); + }); + + it('should treat undefined as a hole', () => { + let Bar = () =>
bar
; + + function Foo(props) { + let sibling; + if (props.condition) { + sibling = ; + } + + return ( +
+
Hello
+ {sibling} +
+ ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
Hello
bar
' + ); + clearLog(); + + render(, scratch); + expect(scratch.innerHTML).to.equal('
Hello
'); + expect(getLog()).to.deep.equal(['
bar.remove()']); + }); + + it('should preserve state of Components when using null or booleans as placeholders', () => { + // Must be the same class for all children in for this test to be valid + class Stateful extends Component { + constructor(props) { + super(props); + this.state = { count: 0 }; + } + increment() { + this.setState({ count: this.state.count + 1 }); + } + componentDidUpdate() { + ops.push(`Update ${this.props.name}`); + } + componentDidMount() { + ops.push(`Mount ${this.props.name}`); + } + componentWillUnmount() { + ops.push(`Unmount ${this.props.name}`); + } + render() { + return ( +
+ {this.props.name}: {this.state.count} +
+ ); + } + } + + const s1ref = createRef(); + const s2ref = createRef(); + const s3ref = createRef(); + + function App({ first = null, second = false }) { + return [first, second, ]; + } + + // Mount third stateful - Initial render + render(, scratch); + expect(scratch.innerHTML).to.equal('
third: 0
'); + expect(ops).to.deep.equal(['Mount third'], 'mount third'); + + // Update third stateful + ops = []; + s3ref.current.increment(); + rerender(); + expect(scratch.innerHTML).to.equal('
third: 1
'); + expect(ops).to.deep.equal(['Update third'], 'update third'); + + // Mount first stateful + ops = []; + render(} />, scratch); + expect(scratch.innerHTML).to.equal( + '
first: 0
third: 1
' + ); + expect(ops).to.deep.equal(['Mount first', 'Update third'], 'mount first'); + + // Update first stateful + ops = []; + s1ref.current.increment(); + s3ref.current.increment(); + rerender(); + expect(scratch.innerHTML).to.equal( + '
first: 1
third: 2
' + ); + expect(ops).to.deep.equal(['Update first', 'Update third'], 'update first'); + + // Mount second stateful + ops = []; + render( + } + second={} + />, + scratch + ); + expect(scratch.innerHTML).to.equal( + '
first: 1
second: 0
third: 2
' + ); + expect(ops).to.deep.equal( + ['Update first', 'Mount second', 'Update third'], + 'mount second' + ); + + // Update second stateful + ops = []; + s1ref.current.increment(); + s2ref.current.increment(); + s3ref.current.increment(); + rerender(); + expect(scratch.innerHTML).to.equal( + '
first: 2
second: 1
third: 3
' + ); + expect(ops).to.deep.equal( + ['Update first', 'Update second', 'Update third'], + 'update second' + ); + }); + + it('should efficiently replace self-updating null placeholders', () => { + // These Nullable components replace themselves with null without the parent re-rendering + const [Nullable, ref] = createStatefulNullable('Nullable'); + const [Nullable2, ref2] = createStatefulNullable('Nullable2'); + function App() { + return ( +
+
1
+ +
3
+ +
+ ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + div([div(1), div('Nullable'), div(3), div('Nullable2')]) + ); + + clearLog(); + ref2.current.toggle(); + ref.current.toggle(); + rerender(); + expect(scratch.innerHTML).to.equal(div([div(1), div(3)])); + expect(getLog()).to.deep.equal([ + '
Nullable2.remove()', + '
Nullable.remove()' + ]); + + clearLog(); + ref2.current.toggle(); + ref.current.toggle(); + rerender(); + expect(scratch.innerHTML).to.equal( + div([div(1), div('Nullable'), div(3), div('Nullable2')]) + ); + expect(getLog()).to.deep.equal([ + '
.appendChild(#text)', + '
13.appendChild(
Nullable2)', + '
.appendChild(#text)', + '
13Nullable2.insertBefore(
Nullable,
3)' + ]); + }); + + // See preactjs/preact#2350 + it('should efficiently replace null placeholders in parent rerenders (#2350)', () => { + // This Nullable only changes when it's parent rerenders + const Nullable1 = createNullable('Nullable 1'); + const Nullable2 = createNullable('Nullable 2'); + + /** @type {() => void} */ + let toggle; + class App extends Component { + constructor(props) { + super(props); + this.state = { show: false }; + toggle = () => this.setState({ show: !this.state.show }); + } + render() { + return ( +
+
{this.state.show.toString()}
+ +
the middle
+ +
+ ); + } + } + + render(, scratch); + expect(scratch.innerHTML).to.equal(div([div('false'), div('the middle')])); + + clearLog(); + toggle(); + rerender(); + expect(scratch.innerHTML).to.equal( + div([div('true'), 'Nullable 1', div('the middle'), 'Nullable 2']) + ); + expect(getLog()).to.deep.equal([ + '
truethe middle.insertBefore(#text,
the middle)', + '
trueNullable 1the middle.appendChild(#text)' + ]); + + clearLog(); + toggle(); + rerender(); + expect(scratch.innerHTML).to.equal(div([div('false'), div('the middle')])); + expect(getLog()).to.deep.equal([ + '#text.remove()', + // '
falsethe middleNullable 2.appendChild(
the middle)', + '#text.remove()' + ]); + }); +}); -- cgit v1.2.3