diff options
Diffstat (limited to 'preact/hooks/test/browser/useState.test.js')
-rw-r--r-- | preact/hooks/test/browser/useState.test.js | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/preact/hooks/test/browser/useState.test.js b/preact/hooks/test/browser/useState.test.js new file mode 100644 index 0000000..c65a21b --- /dev/null +++ b/preact/hooks/test/browser/useState.test.js @@ -0,0 +1,214 @@ +import { setupRerender } from 'preact/test-utils'; +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useState } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('useState', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('serves the same state across render calls', () => { + const stateHistory = []; + + function Comp() { + const [state] = useState({ a: 1 }); + stateHistory.push(state); + return null; + } + + render(<Comp />, scratch); + render(<Comp />, scratch); + + expect(stateHistory).to.deep.equal([{ a: 1 }, { a: 1 }]); + expect(stateHistory[0]).to.equal(stateHistory[1]); + }); + + it('can initialize the state via a function', () => { + const initState = sinon.spy(() => 1); + + function Comp() { + useState(initState); + return null; + } + + render(<Comp />, scratch); + render(<Comp />, scratch); + + expect(initState).to.be.calledOnce; + }); + + it('does not rerender on equal state', () => { + let lastState; + let doSetState; + + const Comp = sinon.spy(() => { + const [state, setState] = useState(0); + lastState = state; + doSetState = setState; + return null; + }); + + render(<Comp />, scratch); + expect(lastState).to.equal(0); + expect(Comp).to.be.calledOnce; + + doSetState(0); + rerender(); + expect(lastState).to.equal(0); + expect(Comp).to.be.calledOnce; + + doSetState(() => 0); + rerender(); + expect(lastState).to.equal(0); + expect(Comp).to.be.calledOnce; + }); + + it('rerenders when setting the state', () => { + let lastState; + let doSetState; + + const Comp = sinon.spy(() => { + const [state, setState] = useState(0); + lastState = state; + doSetState = setState; + return null; + }); + + render(<Comp />, scratch); + expect(lastState).to.equal(0); + expect(Comp).to.be.calledOnce; + + doSetState(1); + rerender(); + expect(lastState).to.equal(1); + expect(Comp).to.be.calledTwice; + + // Updater function style + doSetState(current => current * 10); + rerender(); + expect(lastState).to.equal(10); + expect(Comp).to.be.calledThrice; + }); + + it('can be set by another component', () => { + function StateContainer() { + const [count, setCount] = useState(0); + return ( + <div> + <p>Count: {count}</p> + <Increment increment={() => setCount(c => c + 10)} /> + </div> + ); + } + + function Increment(props) { + return <button onClick={props.increment}>Increment</button>; + } + + render(<StateContainer />, scratch); + expect(scratch.textContent).to.include('Count: 0'); + + const button = scratch.querySelector('button'); + button.click(); + + rerender(); + expect(scratch.textContent).to.include('Count: 10'); + }); + + it('should correctly initialize', () => { + let scopedThing = 'hi'; + let arg; + + function useSomething() { + const args = useState(setup); + function setup(thing = scopedThing) { + arg = thing; + return thing; + } + return args; + } + + const App = () => { + const [state] = useSomething(); + return <p>{state}</p>; + }; + + render(<App />, scratch); + + expect(arg).to.equal('hi'); + expect(scratch.innerHTML).to.equal('<p>hi</p>'); + }); + + it('should correctly re-initialize when first run threw an error', () => { + let hasThrown = false; + let setup = sinon.spy(() => { + if (!hasThrown) { + hasThrown = true; + throw new Error('test'); + } else { + return 'hi'; + } + }); + + const App = () => { + const state = useState(setup)[0]; + return <p>{state}</p>; + }; + + expect(() => render(<App />, scratch)).to.throw('test'); + expect(setup).to.have.been.calledOnce; + expect(() => render(<App />, scratch)).not.to.throw(); + expect(setup).to.have.been.calledTwice; + expect(scratch.innerHTML).to.equal('<p>hi</p>'); + }); + + it('should handle queued useState', () => { + function Message({ message, onClose }) { + const [isVisible, setVisible] = useState(Boolean(message)); + const [prevMessage, setPrevMessage] = useState(message); + + if (message !== prevMessage) { + setPrevMessage(message); + setVisible(Boolean(message)); + } + + if (!isVisible) { + return null; + } + return <p onClick={onClose}>{message}</p>; + } + + function App() { + const [message, setMessage] = useState('Click Here!!'); + return ( + <Message + onClose={() => { + setMessage(''); + }} + message={message} + /> + ); + } + + render(<App />, scratch); + expect(scratch.textContent).to.equal('Click Here!!'); + const text = scratch.querySelector('p'); + text.click(); + rerender(); + expect(scratch.innerHTML).to.equal(''); + }); +}); |