summaryrefslogtreecommitdiff
path: root/preact/hooks/test/browser/combinations.test.js
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/hooks/test/browser/combinations.test.js
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.gz
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.bz2
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.zip
added web vendorsHEADmaster
Diffstat (limited to 'preact/hooks/test/browser/combinations.test.js')
-rw-r--r--preact/hooks/test/browser/combinations.test.js301
1 files changed, 301 insertions, 0 deletions
diff --git a/preact/hooks/test/browser/combinations.test.js b/preact/hooks/test/browser/combinations.test.js
new file mode 100644
index 0000000..986533c
--- /dev/null
+++ b/preact/hooks/test/browser/combinations.test.js
@@ -0,0 +1,301 @@
+import { setupRerender, act } from 'preact/test-utils';
+import { createElement, render, Component } from 'preact';
+import { setupScratch, teardown } from '../../../test/_util/helpers';
+import {
+ useState,
+ useReducer,
+ useEffect,
+ useLayoutEffect,
+ useRef
+} from 'preact/hooks';
+import { scheduleEffectAssert } from '../_util/useEffectUtil';
+
+/** @jsx createElement */
+
+describe('combinations', () => {
+ /** @type {HTMLDivElement} */
+ let scratch;
+
+ /** @type {() => void} */
+ let rerender;
+
+ beforeEach(() => {
+ scratch = setupScratch();
+ rerender = setupRerender();
+ });
+
+ afterEach(() => {
+ teardown(scratch);
+ });
+
+ it('can mix useState hooks', () => {
+ const states = {};
+ const setStates = {};
+
+ function Parent() {
+ const [state1, setState1] = useState(1);
+ const [state2, setState2] = useState(2);
+
+ Object.assign(states, { state1, state2 });
+ Object.assign(setStates, { setState1, setState2 });
+
+ return <Child />;
+ }
+
+ function Child() {
+ const [state3, setState3] = useState(3);
+ const [state4, setState4] = useState(4);
+
+ Object.assign(states, { state3, state4 });
+ Object.assign(setStates, { setState3, setState4 });
+
+ return null;
+ }
+
+ render(<Parent />, scratch);
+ expect(states).to.deep.equal({
+ state1: 1,
+ state2: 2,
+ state3: 3,
+ state4: 4
+ });
+
+ setStates.setState2(n => n * 10);
+ setStates.setState3(n => n * 10);
+ rerender();
+ expect(states).to.deep.equal({
+ state1: 1,
+ state2: 20,
+ state3: 30,
+ state4: 4
+ });
+ });
+
+ it('can rerender asynchronously from within an effect', () => {
+ const didRender = sinon.spy();
+
+ function Comp() {
+ const [counter, setCounter] = useState(0);
+
+ useEffect(() => {
+ if (counter === 0) setCounter(1);
+ });
+
+ didRender(counter);
+ return null;
+ }
+
+ render(<Comp />, scratch);
+
+ return scheduleEffectAssert(() => {
+ rerender();
+ expect(didRender).to.have.been.calledTwice.and.calledWith(1);
+ });
+ });
+
+ it('can rerender synchronously from within a layout effect', () => {
+ const didRender = sinon.spy();
+
+ function Comp() {
+ const [counter, setCounter] = useState(0);
+
+ useLayoutEffect(() => {
+ if (counter === 0) setCounter(1);
+ });
+
+ didRender(counter);
+ return null;
+ }
+
+ render(<Comp />, scratch);
+ rerender();
+
+ expect(didRender).to.have.been.calledTwice.and.calledWith(1);
+ });
+
+ it('can access refs from within a layout effect callback', () => {
+ let refAtLayoutTime;
+
+ function Comp() {
+ const input = useRef();
+
+ useLayoutEffect(() => {
+ refAtLayoutTime = input.current;
+ });
+
+ return <input ref={input} value="hello" />;
+ }
+
+ render(<Comp />, scratch);
+
+ expect(refAtLayoutTime.value).to.equal('hello');
+ });
+
+ it('can use multiple useState and useReducer hooks', () => {
+ let states = [];
+ let dispatchState4;
+
+ function reducer1(state, action) {
+ switch (action.type) {
+ case 'increment':
+ return state + action.count;
+ }
+ }
+
+ function reducer2(state, action) {
+ switch (action.type) {
+ case 'increment':
+ return state + action.count * 2;
+ }
+ }
+
+ function Comp() {
+ const [state1] = useState(0);
+ const [state2] = useReducer(reducer1, 10);
+ const [state3] = useState(1);
+ const [state4, dispatch] = useReducer(reducer2, 20);
+
+ dispatchState4 = dispatch;
+ states.push(state1, state2, state3, state4);
+
+ return null;
+ }
+
+ render(<Comp />, scratch);
+
+ expect(states).to.deep.equal([0, 10, 1, 20]);
+
+ states = [];
+
+ dispatchState4({ type: 'increment', count: 10 });
+ rerender();
+
+ expect(states).to.deep.equal([0, 10, 1, 40]);
+ });
+
+ it('ensures useEffect always schedule after the next paint following a redraw effect, when using the default debounce strategy', () => {
+ let effectCount = 0;
+
+ function Comp() {
+ const [counter, setCounter] = useState(0);
+
+ useEffect(() => {
+ if (counter === 0) setCounter(1);
+ effectCount++;
+ });
+
+ return null;
+ }
+
+ render(<Comp />, scratch);
+
+ return scheduleEffectAssert(() => {
+ expect(effectCount).to.equal(1);
+ });
+ });
+
+ it('should not reuse functional components with hooks', () => {
+ let updater = { first: undefined, second: undefined };
+ function Foo(props) {
+ let [v, setter] = useState(0);
+ updater[props.id] = () => setter(++v);
+ return <div>{v}</div>;
+ }
+
+ let updateParent;
+ class App extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { active: true };
+ updateParent = () => this.setState(p => ({ active: !p.active }));
+ }
+
+ render() {
+ return (
+ <div>
+ {this.state.active && <Foo id="first" />}
+ <Foo id="second" />
+ </div>
+ );
+ }
+ }
+
+ render(<App />, scratch);
+ act(() => updater.second());
+ expect(scratch.textContent).to.equal('01');
+
+ updateParent();
+ rerender();
+ expect(scratch.textContent).to.equal('1');
+
+ updateParent();
+ rerender();
+
+ expect(scratch.textContent).to.equal('01');
+ });
+
+ it('should have a right call order with correct dom ref', () => {
+ let i = 0,
+ set;
+ const calls = [];
+
+ function Inner() {
+ useLayoutEffect(() => {
+ calls.push('layout inner call ' + scratch.innerHTML);
+ return () => calls.push('layout inner dispose ' + scratch.innerHTML);
+ });
+ useEffect(() => {
+ calls.push('effect inner call ' + scratch.innerHTML);
+ return () => calls.push('effect inner dispose ' + scratch.innerHTML);
+ });
+ return <span>hello {i}</span>;
+ }
+
+ function Outer() {
+ i++;
+ const [state, setState] = useState(false);
+ set = () => setState(!state);
+ useLayoutEffect(() => {
+ calls.push('layout outer call ' + scratch.innerHTML);
+ return () => calls.push('layout outer dispose ' + scratch.innerHTML);
+ });
+ useEffect(() => {
+ calls.push('effect outer call ' + scratch.innerHTML);
+ return () => calls.push('effect outer dispose ' + scratch.innerHTML);
+ });
+ return <Inner />;
+ }
+
+ act(() => render(<Outer />, scratch));
+ expect(calls).to.deep.equal([
+ 'layout inner call <span>hello 1</span>',
+ 'layout outer call <span>hello 1</span>',
+ 'effect inner call <span>hello 1</span>',
+ 'effect outer call <span>hello 1</span>'
+ ]);
+
+ // NOTE: this order is (at the time of writing) intentionally different from
+ // React. React calls all disposes across all components, and then invokes all
+ // effects across all components. We call disposes and effects in order of components:
+ // for each component, call its disposes and then its effects. If presented with a
+ // compelling use case to support inter-component dispose dependencies, then rewrite this
+ // test to test React's order. In other words, if there is a use case to support calling
+ // all disposes across components then re-order the lines below to demonstrate the desired behavior.
+
+ act(() => set());
+ expect(calls).to.deep.equal([
+ 'layout inner call <span>hello 1</span>',
+ 'layout outer call <span>hello 1</span>',
+ 'effect inner call <span>hello 1</span>',
+ 'effect outer call <span>hello 1</span>',
+ 'layout inner dispose <span>hello 2</span>',
+ 'layout inner call <span>hello 2</span>',
+ 'layout outer dispose <span>hello 2</span>',
+ 'layout outer call <span>hello 2</span>',
+ 'effect inner dispose <span>hello 2</span>',
+ 'effect inner call <span>hello 2</span>',
+ 'effect outer dispose <span>hello 2</span>',
+ 'effect outer call <span>hello 2</span>'
+ ]);
+ });
+});