From 3a107f0a576d85d475e37623824414cfa197ce17 Mon Sep 17 00:00:00 2001 From: Jovi De Croock <decroockjovi@gmail.com> Date: Thu, 21 Mar 2019 12:10:11 +0100 Subject: [PATCH 1/4] tests: provide test for bugging scenario --- test-utils/test/shared/act.test.js | 50 +++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/test-utils/test/shared/act.test.js b/test-utils/test/shared/act.test.js index baf03c2aa5..e5fbb3806e 100644 --- a/test-utils/test/shared/act.test.js +++ b/test-utils/test/shared/act.test.js @@ -1,5 +1,6 @@ import { options, createElement as h, render } from 'preact'; import { useEffect, useState } from 'preact/hooks'; +import sinon from 'sinon'; import { setupScratch, teardown } from '../../../test/_util/helpers'; import { act } from '../../src'; @@ -24,9 +25,47 @@ describe('act', () => { 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 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)} /> @@ -43,17 +82,6 @@ describe('act', () => { expect(scratch.textContent).to.include('Count: 1'); }); - 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 restore options.requestAnimationFrame', () => { const spy = sinon.spy(); From ad1afcdbaf2a1efc33bf0abd2726e328fad6af61 Mon Sep 17 00:00:00 2001 From: Jovi De Croock <decroockjovi@gmail.com> Date: Thu, 21 Mar 2019 12:10:47 +0100 Subject: [PATCH 2/4] fix: solve scenario where a state update triggered an effect, this effect would be queued for next render and thus not be executed --- test-utils/src/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test-utils/src/index.js b/test-utils/src/index.js index 270147926b..77b2ffe2f2 100644 --- a/test-utils/src/index.js +++ b/test-utils/src/index.js @@ -11,16 +11,24 @@ export function setupRerender() { } export function act(cb) { - const previousDebounce = options.debounceRendering; const previousRequestAnimationFrame = options.requestAnimationFrame; const rerender = setupRerender(); let flush; + // Override requestAnimationFrame so we can flush pending hooks. options.requestAnimationFrame = (fc) => flush = fc; + // Execute the callback we were passed. cb(); + // State COULD be built up flush it. if (flush) { flush(); } + // Rerender rerender(); - options.debounceRendering = previousDebounce; + // If rerendering with new state has triggered effects + // flush them aswell since options.raf will have repopulated this. + if (flush) { + flush(); + } + options.debounceRendering = Component.__test__previousDebounce; options.requestAnimationFrame = previousRequestAnimationFrame; } From 1c5a777a5b2318248e0423f514ab67b21ca9d2f0 Mon Sep 17 00:00:00 2001 From: Jovi De Croock <decroockjovi@gmail.com> Date: Thu, 21 Mar 2019 17:55:34 +0100 Subject: [PATCH 3/4] follow pr comments --- test-utils/src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test-utils/src/index.js b/test-utils/src/index.js index 77b2ffe2f2..fb5457eea9 100644 --- a/test-utils/src/index.js +++ b/test-utils/src/index.js @@ -22,7 +22,6 @@ export function act(cb) { if (flush) { flush(); } - // Rerender rerender(); // If rerendering with new state has triggered effects // flush them aswell since options.raf will have repopulated this. From 99e848fe01b263ee05a0dab725f2b5b9a5325a91 Mon Sep 17 00:00:00 2001 From: Jovi De Croock <decroockjovi@gmail.com> Date: Thu, 21 Mar 2019 17:56:04 +0100 Subject: [PATCH 4/4] remove sinon --- test-utils/test/shared/act.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test-utils/test/shared/act.test.js b/test-utils/test/shared/act.test.js index e5fbb3806e..f0e410ee53 100644 --- a/test-utils/test/shared/act.test.js +++ b/test-utils/test/shared/act.test.js @@ -1,6 +1,5 @@ import { options, createElement as h, render } from 'preact'; import { useEffect, useState } from 'preact/hooks'; -import sinon from 'sinon'; import { setupScratch, teardown } from '../../../test/_util/helpers'; import { act } from '../../src';