import { mount } from 'enzyme'; import React from 'react'; import type { IUseLatestPromiseResult } from './useLatestPromise.hook'; import { useLatestPromise } from './useLatestPromise.hook'; describe('useLatestPromise hook', () => { // Remove the the refresh function for .isEqual assertions function promiseState(call: IUseLatestPromiseResult) { const { refresh, ...state } = call; return state; } function Component(props: any) { const { promiseFactory, deps, onChange } = props; const useLatestPromiseResult: IUseLatestPromiseResult = useLatestPromise(promiseFactory, deps); const { status, result, error, requestId } = useLatestPromiseResult; React.useEffect(() => onChange(useLatestPromiseResult), [status, result, error, requestId]); return <>; } function defer() { let resolve: Function, reject: Function; const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return { promise, resolve, reject }; } it('has status NONE if no promise has been returned', () => { const spy = jasmine.createSpy('onChange'); mount( null as any} deps={[]} onChange={spy} />); expect(spy).toHaveBeenCalledTimes(1); const result: IUseLatestPromiseResult = spy.calls.mostRecent().args[0]; expect(promiseState(result)).toEqual({ status: 'NONE', result: undefined, error: undefined, requestId: 0 }); }); it('has status PENDING if a promise has been returned but has not yet resolved', () => { const spy = jasmine.createSpy('onChange'); const deferred = defer(); mount( deferred.promise} deps={[]} onChange={spy} />); expect(spy).toHaveBeenCalledTimes(2); const result: IUseLatestPromiseResult = spy.calls.mostRecent().args[0]; expect(promiseState(result)).toEqual({ status: 'PENDING', result: undefined, error: undefined, requestId: 0 }); }); it('has status RESOLVED if a promise resolved', async () => { const spy = jasmine.createSpy('onChange'); const deferred = defer(); const component = mount( deferred.promise} deps={[]} onChange={spy} />); expect(spy).toHaveBeenCalledTimes(2); deferred.resolve('payload'); await deferred.promise; component.setProps({}); expect(spy).toHaveBeenCalledTimes(3); const result: IUseLatestPromiseResult = spy.calls.mostRecent().args[0]; expect(promiseState(result)).toEqual({ status: 'RESOLVED', result: 'payload', error: undefined, requestId: 0 }); }); it('has status REJECTED if a promise rejected', async () => { const spy = jasmine.createSpy('onChange'); const deferred = defer(); const component = mount( deferred.promise} deps={[]} onChange={spy} />); expect(spy).toHaveBeenCalledTimes(2); deferred.reject('error'); let caught = false; try { await deferred.promise; } catch (error) { caught = true; } expect(caught).toBe(true); component.setProps({}); expect(spy).toHaveBeenCalledTimes(3); const result: IUseLatestPromiseResult = spy.calls.mostRecent().args[0]; expect(promiseState(result)).toEqual({ status: 'REJECTED', result: undefined, error: 'error', requestId: 0 }); }); it('only handles the latest promise when multiple promises are pending', async () => { const spy = jasmine.createSpy('onChange'); const deferred1 = defer(); const component = mount( deferred1.promise} deps={[1]} onChange={spy} />); expect(spy).toHaveBeenCalledTimes(2); const deferred2 = defer(); component.setProps({ promiseFactory: () => deferred2.promise, deps: [2] }); component.setProps({}); expect(spy).toHaveBeenCalledTimes(3); expect(spy.calls.mostRecent().args[0].status).toEqual('PENDING'); deferred1.resolve('payload1'); await deferred1.promise; component.setProps({}); expect(spy).toHaveBeenCalledTimes(3); expect(spy.calls.mostRecent().args[0].status).toEqual('PENDING'); deferred2.resolve('payload2'); await deferred2.promise; component.setProps({}); expect(spy).toHaveBeenCalledTimes(4); const result: IUseLatestPromiseResult = spy.calls.mostRecent().args[0]; expect(promiseState(result)).toEqual({ status: 'RESOLVED', result: 'payload2', error: undefined, requestId: 1 }); }); it('gets a new promise if refresh() is called', async () => { const spy = jasmine.createSpy('onChange'); const deferred = defer(); const promiseFactorySpy = jasmine.createSpy('promiseFactory').and.callFake(() => deferred.promise); const component = mount(); expect(promiseFactorySpy).toHaveBeenCalledTimes(1); // initial promise is resolved. deferred.resolve('payload'); await deferred.promise; component.setProps({}); spy.calls.mostRecent().args[0].refresh(); component.setProps({}); expect(promiseFactorySpy).toHaveBeenCalledTimes(2); }); it('ignores old pending results if a newer promise is being processed', async () => { const spy = jasmine.createSpy('onChange'); const deferred1 = defer(); const deferred2 = defer(); const component = mount( deferred1.promise} deps={[1]} onChange={spy} />); component.setProps({ promiseFactory: () => deferred2.promise, deps: [2] }); // The first promise is resolved. deferred1.resolve('payload1'); await deferred1.promise; component.setProps({}); // The second promise is resolved. deferred2.resolve('payload2'); await deferred2.promise; component.setProps({}); expect(spy).toHaveBeenCalledTimes(4); const allCalls = spy.calls.allArgs().map((args) => promiseState(args[0])); expect(allCalls[0]).toEqual({ status: 'NONE', result: undefined, error: undefined, requestId: 0 }); // initial request expect(allCalls[1]).toEqual({ status: 'PENDING', result: undefined, error: undefined, requestId: 0 }); // second request expect(allCalls[2]).toEqual({ status: 'PENDING', result: undefined, error: undefined, requestId: 1 }); // resolved second request expect(allCalls[3]).toEqual({ status: 'RESOLVED', result: 'payload2', error: undefined, requestId: 1 }); }); });