forked from spinnaker/deck
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core/presentation): Reduce unnecessary renders in useLatestPromi…
…se, add tests
- Loading branch information
1 parent
90b249d
commit a10e068
Showing
2 changed files
with
191 additions
and
22 deletions.
There are no files selected for viewing
161 changes: 161 additions & 0 deletions
161
app/scripts/modules/core/src/presentation/hooks/useLatestPromise.hook.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import * as React from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { IUseLatestPromiseResult, useLatestPromise } from './useLatestPromise.hook'; | ||
|
||
describe('useLatestPromise hook', () => { | ||
// Remove the the refresh function for .isEqual assertions | ||
function promiseState(call: IUseLatestPromiseResult<any>) { | ||
const { refresh, ...state } = call; | ||
return state; | ||
} | ||
|
||
function Component(props: any) { | ||
const { promiseFactory, deps, onChange } = props; | ||
const useLatestPromiseResult: IUseLatestPromiseResult<any> = 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(<Component promiseFactory={() => null as any} deps={[]} onChange={spy} />); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
|
||
const result: IUseLatestPromiseResult<any> = 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(<Component promiseFactory={() => deferred.promise} deps={[]} onChange={spy} />); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
|
||
const result: IUseLatestPromiseResult<any> = 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 done => { | ||
const spy = jasmine.createSpy('onChange'); | ||
const deferred = defer(); | ||
const component = mount(<Component promiseFactory={() => deferred.promise} deps={[]} onChange={spy} />); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
|
||
deferred.resolve('payload'); | ||
await deferred.promise; | ||
component.setProps({}); | ||
|
||
expect(spy).toHaveBeenCalledTimes(3); | ||
const result: IUseLatestPromiseResult<any> = spy.calls.mostRecent().args[0]; | ||
expect(promiseState(result)).toEqual({ status: 'RESOLVED', result: 'payload', error: undefined, requestId: 0 }); | ||
done(); | ||
}); | ||
|
||
it('has status REJECTED if a promise rejected', async done => { | ||
const spy = jasmine.createSpy('onChange'); | ||
const deferred = defer(); | ||
const component = mount(<Component promiseFactory={() => deferred.promise} deps={[]} onChange={spy} />); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
|
||
deferred.reject('error'); | ||
try { | ||
await deferred.promise; | ||
} catch (error) {} | ||
component.setProps({}); | ||
|
||
expect(spy).toHaveBeenCalledTimes(3); | ||
const result: IUseLatestPromiseResult<any> = spy.calls.mostRecent().args[0]; | ||
expect(promiseState(result)).toEqual({ status: 'REJECTED', result: undefined, error: 'error', requestId: 0 }); | ||
done(); | ||
}); | ||
|
||
it('only handles the latest promise when multiple promises are pending', async done => { | ||
const spy = jasmine.createSpy('onChange'); | ||
const deferred1 = defer(); | ||
const component = mount(<Component promiseFactory={() => 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<any> = spy.calls.mostRecent().args[0]; | ||
expect(promiseState(result)).toEqual({ status: 'RESOLVED', result: 'payload2', error: undefined, requestId: 1 }); | ||
done(); | ||
}); | ||
|
||
it('gets a new promise if refresh() is called', async done => { | ||
const spy = jasmine.createSpy('onChange'); | ||
const deferred = defer(); | ||
const promiseFactorySpy = jasmine.createSpy('promiseFactory').and.callFake(() => deferred.promise); | ||
const component = mount(<Component promiseFactory={promiseFactorySpy} deps={[]} onChange={spy} />); | ||
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); | ||
|
||
done(); | ||
}); | ||
|
||
it('ignores old pending results if a newer promise is being processed', async done => { | ||
const spy = jasmine.createSpy('onChange'); | ||
const deferred1 = defer(); | ||
const deferred2 = defer(); | ||
const component = mount(<Component promiseFactory={() => 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 }); | ||
|
||
done(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters