summaryrefslogtreecommitdiff
path: root/preact/test-utils/test/shared
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-08-23 16:46:06 -0300
committerSebastian <sebasjm@gmail.com>2021-08-23 16:48:30 -0300
commit38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch)
tree453dbf70000cc5e338b06201af1eaca8343f8f73 /preact/test-utils/test/shared
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.gz
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.bz2
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.zip
added web vendorsHEADmaster
Diffstat (limited to 'preact/test-utils/test/shared')
-rw-r--r--preact/test-utils/test/shared/act.test.js479
-rw-r--r--preact/test-utils/test/shared/rerender.test.js59
2 files changed, 538 insertions, 0 deletions
diff --git a/preact/test-utils/test/shared/act.test.js b/preact/test-utils/test/shared/act.test.js
new file mode 100644
index 0000000..7769b5b
--- /dev/null
+++ b/preact/test-utils/test/shared/act.test.js
@@ -0,0 +1,479 @@
+import { options, createElement, render } from 'preact';
+import { useEffect, useReducer, useState } from 'preact/hooks';
+import { act } from 'preact/test-utils';
+import { setupScratch, teardown } from '../../../test/_util/helpers';
+
+/** @jsx createElement */
+
+// IE11 doesn't support `new Event()`
+function createEvent(name) {
+ if (typeof Event == 'function') return new Event(name);
+
+ const event = document.createEvent('Event');
+ event.initEvent(name, true, true);
+ return event;
+}
+
+describe('act', () => {
+ /** @type {HTMLDivElement} */
+ let scratch;
+
+ beforeEach(() => {
+ scratch = setupScratch();
+ });
+
+ afterEach(() => {
+ teardown(scratch);
+ options.debounceRendering = undefined;
+ });
+
+ it('should reset options after act finishes', () => {
+ expect(options.requestAnimationFrame).to.equal(undefined);
+ act(() => null);
+ expect(options.requestAnimationFrame).to.equal(undefined);
+ });
+
+ it('should flush pending effects', () => {
+ let spy = sinon.spy();
+ function StateContainer() {
+ useEffect(spy);
+ return <div />;
+ }
+ act(() => render(<StateContainer />, scratch));
+ expect(spy).to.be.calledOnce;
+ });
+
+ it('should flush pending and initial effects', () => {
+ const spy = sinon.spy();
+ function StateContainer() {
+ const [count, setCount] = useState(0);
+ useEffect(() => spy(), [count]);
+ return (
+ <div>
+ <p>Count: {count}</p>
+ <button onClick={() => setCount(c => c + 11)} />
+ </div>
+ );
+ }
+
+ act(() => render(<StateContainer />, scratch));
+ expect(spy).to.be.calledOnce;
+ expect(scratch.textContent).to.include('Count: 0');
+ act(() => {
+ const button = scratch.querySelector('button');
+ button.click();
+ expect(spy).to.be.calledOnce;
+ expect(scratch.textContent).to.include('Count: 0');
+ });
+ expect(spy).to.be.calledTwice;
+ expect(scratch.textContent).to.include('Count: 1');
+ });
+
+ it('should flush series of hooks', () => {
+ const spy = sinon.spy();
+ const spy2 = sinon.spy();
+ function StateContainer() {
+ const [count, setCount] = useState(0);
+ useEffect(() => {
+ spy();
+ if (count === 1) {
+ setCount(() => 2);
+ }
+ }, [count]);
+ useEffect(() => {
+ if (count === 2) {
+ spy2();
+ setCount(() => 4);
+ return () => setCount(() => 3);
+ }
+ }, [count]);
+ return (
+ <div>
+ <p>Count: {count}</p>
+ <button onClick={() => setCount(c => c + 1)} />
+ </div>
+ );
+ }
+ act(() => render(<StateContainer />, scratch));
+ expect(spy).to.be.calledOnce;
+ expect(scratch.textContent).to.include('Count: 0');
+ act(() => {
+ const button = scratch.querySelector('button');
+ button.click();
+ });
+ expect(spy.callCount).to.equal(5);
+ expect(spy2).to.be.calledOnce;
+ expect(scratch.textContent).to.include('Count: 3');
+ });
+
+ it('should drain the queue of hooks', () => {
+ const spy = sinon.spy();
+ function StateContainer() {
+ const [count, setCount] = useState(0);
+ useEffect(() => spy());
+ return (
+ <div>
+ <p>Count: {count}</p>
+ <button onClick={() => setCount(c => c + 11)} />
+ </div>
+ );
+ }
+
+ render(<StateContainer />, scratch);
+ expect(scratch.textContent).to.include('Count: 0');
+ act(() => {
+ const button = scratch.querySelector('button');
+ button.click();
+ expect(scratch.textContent).to.include('Count: 0');
+ });
+ expect(scratch.textContent).to.include('Count: 1');
+ });
+
+ it('should restore options.requestAnimationFrame', () => {
+ const spy = sinon.spy();
+
+ options.requestAnimationFrame = spy;
+ act(() => null);
+
+ expect(options.requestAnimationFrame).to.equal(spy);
+ expect(spy).to.not.be.called;
+ });
+
+ it('should restore options.debounceRendering', () => {
+ const spy = sinon.spy();
+
+ options.debounceRendering = spy;
+ act(() => null);
+
+ expect(options.debounceRendering).to.equal(spy);
+ expect(spy).to.not.be.called;
+ });
+
+ it('should restore options.debounceRendering when it was undefined before', () => {
+ act(() => null);
+ expect(options.debounceRendering).to.equal(undefined);
+ });
+
+ it('should flush state updates if there are pending state updates before `act` call', () => {
+ function CounterButton() {
+ const [count, setCount] = useState(0);
+ const increment = () => setCount(count => count + 1);
+ return <button onClick={increment}>{count}</button>;
+ }
+
+ render(<CounterButton />, scratch);
+ const button = scratch.querySelector('button');
+
+ // Click button. This will schedule an update which is deferred, as is
+ // normal for Preact, since it happens outside an `act` call.
+ button.dispatchEvent(createEvent('click'));
+
+ expect(button.textContent).to.equal('0');
+
+ act(() => {
+ // Click button a second time. This will schedule a second update.
+ button.dispatchEvent(createEvent('click'));
+ });
+ // All state updates should be applied synchronously after the `act`
+ // callback has run but before `act` returns.
+ expect(button.textContent).to.equal('2');
+ });
+
+ it('should flush effects if there are pending effects before `act` call', () => {
+ function Counter() {
+ const [count, setCount] = useState(0);
+ useEffect(() => {
+ setCount(count => count + 1);
+ }, []);
+ return <div>{count}</div>;
+ }
+
+ // Render a component which schedules an effect outside of an `act`
+ // call. This will be scheduled to execute after the next paint as usual.
+ render(<Counter />, scratch);
+ expect(scratch.firstChild.textContent).to.equal('0');
+
+ // Render a component inside an `act` call, this effect should be
+ // executed synchronously before `act` returns.
+ act(() => {
+ render(<div />, scratch);
+ render(<Counter />, scratch);
+ });
+ expect(scratch.firstChild.textContent).to.equal('1');
+ });
+
+ it('returns a Promise if invoked with a sync callback', () => {
+ const result = act(() => {});
+ expect(result.then).to.be.a('function');
+ return result;
+ });
+
+ it('returns a Promise if invoked with an async callback', () => {
+ const result = act(async () => {});
+ expect(result.then).to.be.a('function');
+ return result;
+ });
+
+ it('should await "thenable" result of callback before flushing', async () => {
+ const events = [];
+
+ function TestComponent() {
+ useEffect(() => {
+ events.push('flushed effect');
+ }, []);
+ events.push('scheduled effect');
+ return <div>Test</div>;
+ }
+
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+ events.push('began test');
+ const acted = act(async () => {
+ events.push('began act callback');
+ await delay(1);
+ render(<TestComponent />, scratch);
+ events.push('end act callback');
+ });
+ events.push('act returned');
+ await acted;
+ events.push('act result resolved');
+
+ expect(events).to.deep.equal([
+ 'began test',
+ 'began act callback',
+ 'act returned',
+ 'scheduled effect',
+ 'end act callback',
+ 'flushed effect',
+ 'act result resolved'
+ ]);
+ });
+
+ context('when `act` calls are nested', () => {
+ it('should invoke nested sync callback and return a Promise', () => {
+ let innerResult;
+ const spy = sinon.stub();
+
+ act(() => {
+ innerResult = act(spy);
+ });
+
+ expect(spy).to.be.calledOnce;
+ expect(innerResult.then).to.be.a('function');
+ });
+
+ it('should invoke nested async callback and return a Promise', async () => {
+ const events = [];
+
+ await act(async () => {
+ events.push('began outer act callback');
+ await act(async () => {
+ events.push('began inner act callback');
+ await Promise.resolve();
+ events.push('end inner act callback');
+ });
+ events.push('end outer act callback');
+ });
+ events.push('act finished');
+
+ expect(events).to.deep.equal([
+ 'began outer act callback',
+ 'began inner act callback',
+ 'end inner act callback',
+ 'end outer act callback',
+ 'act finished'
+ ]);
+ });
+
+ it('should only flush effects when outer `act` call returns', () => {
+ let counter = 0;
+
+ function Widget() {
+ useEffect(() => {
+ ++counter;
+ });
+ const [, forceUpdate] = useReducer(x => x + 1, 0);
+ return <button onClick={forceUpdate}>test</button>;
+ }
+
+ act(() => {
+ render(<Widget />, scratch);
+ const button = scratch.querySelector('button');
+ expect(counter).to.equal(0);
+
+ act(() => {
+ button.dispatchEvent(createEvent('click'));
+ });
+
+ // Effect triggered by inner `act` call should not have been
+ // flushed yet.
+ expect(counter).to.equal(0);
+ });
+
+ // Effects triggered by inner `act` call should now have been
+ // flushed.
+ expect(counter).to.equal(2);
+ });
+
+ it('should only flush updates when outer `act` call returns', () => {
+ function Button() {
+ const [count, setCount] = useState(0);
+ const increment = () => setCount(count => count + 1);
+ return <button onClick={increment}>{count}</button>;
+ }
+
+ render(<Button />, scratch);
+ const button = scratch.querySelector('button');
+ expect(button.textContent).to.equal('0');
+
+ act(() => {
+ act(() => {
+ button.dispatchEvent(createEvent('click'));
+ });
+
+ // Update triggered by inner `act` call should not have been
+ // flushed yet.
+ expect(button.textContent).to.equal('0');
+ });
+
+ // Updates from outer and inner `act` calls should now have been
+ // flushed.
+ expect(button.textContent).to.equal('1');
+ });
+ });
+
+ describe('when `act` callback throws an exception', () => {
+ function BrokenWidget() {
+ throw new Error('BrokenWidget is broken');
+ }
+
+ let effectCount;
+
+ function WorkingWidget() {
+ const [count, setCount] = useState(0);
+
+ useEffect(() => {
+ ++effectCount;
+ }, []);
+
+ if (count === 0) {
+ setCount(1);
+ }
+
+ return <div>{count}</div>;
+ }
+
+ beforeEach(() => {
+ effectCount = 0;
+ });
+
+ const renderBroken = () => {
+ act(() => {
+ render(<BrokenWidget />, scratch);
+ });
+ };
+
+ const renderWorking = () => {
+ act(() => {
+ render(<WorkingWidget />, scratch);
+ });
+ };
+
+ const tryRenderBroken = () => {
+ try {
+ renderBroken();
+ } catch (e) {}
+ };
+
+ describe('synchronously', () => {
+ it('should rethrow the exception', () => {
+ expect(renderBroken).to.throw('BrokenWidget is broken');
+ });
+
+ it('should not affect state updates in future renders', () => {
+ tryRenderBroken();
+ renderWorking();
+ expect(scratch.textContent).to.equal('1');
+ });
+
+ it('should not affect effects in future renders', () => {
+ tryRenderBroken();
+ renderWorking();
+ expect(effectCount).to.equal(1);
+ });
+ });
+
+ describe('asynchronously', () => {
+ const renderBrokenAsync = async () => {
+ await act(async () => {
+ render(<BrokenWidget />, scratch);
+ });
+ };
+
+ it('should rethrow the exception', async () => {
+ let err;
+ try {
+ await renderBrokenAsync();
+ } catch (e) {
+ err = e;
+ }
+ expect(err.message).to.equal('BrokenWidget is broken');
+ });
+
+ it('should not affect state updates in future renders', async () => {
+ try {
+ await renderBrokenAsync();
+ } catch (e) {}
+
+ renderWorking();
+ expect(scratch.textContent).to.equal('1');
+ });
+
+ it('should not affect effects in future renders', async () => {
+ try {
+ await renderBrokenAsync();
+ } catch (e) {}
+
+ renderWorking();
+ expect(effectCount).to.equal(1);
+ });
+ });
+
+ describe('in an effect', () => {
+ function BrokenEffect() {
+ useEffect(() => {
+ throw new Error('BrokenEffect effect');
+ }, []);
+ return null;
+ }
+
+ const renderBrokenEffect = () => {
+ act(() => {
+ render(<BrokenEffect />, scratch);
+ });
+ };
+
+ it('should rethrow the exception', () => {
+ expect(renderBrokenEffect).to.throw('BrokenEffect effect');
+ });
+
+ it('should not affect state updates in future renders', () => {
+ try {
+ renderBrokenEffect();
+ } catch (e) {}
+
+ renderWorking();
+ expect(scratch.textContent).to.equal('1');
+ });
+
+ it('should not affect effects in future renders', () => {
+ try {
+ renderBrokenEffect();
+ } catch (e) {}
+
+ renderWorking();
+ expect(effectCount).to.equal(1);
+ });
+ });
+ });
+});
diff --git a/preact/test-utils/test/shared/rerender.test.js b/preact/test-utils/test/shared/rerender.test.js
new file mode 100644
index 0000000..a2e4335
--- /dev/null
+++ b/preact/test-utils/test/shared/rerender.test.js
@@ -0,0 +1,59 @@
+import { options, createElement, render, Component } from 'preact';
+import { teardown, setupRerender } from 'preact/test-utils';
+
+/** @jsx createElement */
+
+describe('setupRerender & teardown', () => {
+ /** @type {HTMLDivElement} */
+ let scratch;
+
+ beforeEach(() => {
+ scratch = document.createElement('div');
+ });
+
+ it('should restore previous debounce', () => {
+ let spy = (options.debounceRendering = sinon.spy());
+
+ setupRerender();
+ teardown();
+
+ expect(options.debounceRendering).to.equal(spy);
+ });
+
+ it('teardown should flush the queue', () => {
+ /** @type {() => void} */
+ let increment;
+ class Counter extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = { count: 0 };
+ increment = () => this.setState({ count: this.state.count + 1 });
+ }
+
+ render() {
+ return <div>{this.state.count}</div>;
+ }
+ }
+
+ sinon.spy(Counter.prototype, 'render');
+
+ // Setup rerender
+ setupRerender();
+
+ // Initial render
+ render(<Counter />, scratch);
+ expect(Counter.prototype.render).to.have.been.calledOnce;
+ expect(scratch.innerHTML).to.equal('<div>0</div>');
+
+ // queue rerender
+ increment();
+ expect(Counter.prototype.render).to.have.been.calledOnce;
+ expect(scratch.innerHTML).to.equal('<div>0</div>');
+
+ // Pretend test forgot to call rerender. Teardown should do that
+ teardown();
+ expect(Counter.prototype.render).to.have.been.calledTwice;
+ expect(scratch.innerHTML).to.equal('<div>1</div>');
+ });
+});