-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Cleaned up the useObjectFetch tests as well - Still not using the useWidget hook, will use from deephaven-plugin-ui
- Loading branch information
Showing
7 changed files
with
177 additions
and
27 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
35 changes: 11 additions & 24 deletions
35
...sapi-bootstrap/src/useObjectFetch.test.ts → ...api-bootstrap/src/useObjectFetch.test.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
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,68 @@ | ||
import React from 'react'; | ||
import { act, renderHook } from '@testing-library/react-hooks'; | ||
import { TestUtils } from '@deephaven/utils'; | ||
import { useWidget } from './useWidget'; | ||
import { ObjectFetchManagerContext } from './useObjectFetch'; | ||
|
||
describe('useWidget', () => { | ||
it('should return a widget when available', async () => { | ||
const descriptor = { type: 'type', name: 'name' }; | ||
const widget = { close: jest.fn() }; | ||
const fetch = jest.fn(async () => widget); | ||
const objectFetch = { fetch, error: null }; | ||
const subscribe = jest.fn((subscribeDescriptor, onUpdate) => { | ||
expect(subscribeDescriptor).toEqual(descriptor); | ||
onUpdate(objectFetch); | ||
return jest.fn(); | ||
}); | ||
const objectManager = { subscribe }; | ||
const wrapper = ({ children }) => ( | ||
<ObjectFetchManagerContext.Provider value={objectManager}> | ||
{children} | ||
</ObjectFetchManagerContext.Provider> | ||
); | ||
const { result } = renderHook(() => useWidget(descriptor), { wrapper }); | ||
await act(TestUtils.flushPromises); | ||
expect(result.current).toEqual({ widget, error: null }); | ||
expect(fetch).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return an error when an error occurs', () => { | ||
const descriptor = { type: 'type', name: 'name' }; | ||
const error = new Error('Error fetching widget'); | ||
const objectFetch = { fetch: null, error }; | ||
const subscribe = jest.fn((subscribeDescriptor, onUpdate) => { | ||
expect(subscribeDescriptor).toEqual(descriptor); | ||
onUpdate(objectFetch); | ||
return jest.fn(); | ||
}); | ||
const objectManager = { subscribe }; | ||
const wrapper = ({ children }) => ( | ||
<ObjectFetchManagerContext.Provider value={objectManager}> | ||
{children} | ||
</ObjectFetchManagerContext.Provider> | ||
); | ||
|
||
const { result } = renderHook(() => useWidget(descriptor), { wrapper }); | ||
|
||
expect(result.current).toEqual({ widget: null, error }); | ||
}); | ||
|
||
it('should return null when still loading', () => { | ||
const descriptor = { type: 'type', name: 'name' }; | ||
const objectFetch = { fetch: null, error: null }; | ||
const subscribe = jest.fn((_, onUpdate) => { | ||
onUpdate(objectFetch); | ||
return jest.fn(); | ||
}); | ||
const objectManager = { subscribe }; | ||
const wrapper = ({ children }) => ( | ||
<ObjectFetchManagerContext.Provider value={objectManager}> | ||
{children} | ||
</ObjectFetchManagerContext.Provider> | ||
); | ||
const { result } = renderHook(() => useWidget(descriptor), { wrapper }); | ||
|
||
expect(result.current).toEqual({ widget: null, error: null }); | ||
}); | ||
}); |
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,90 @@ | ||
import { dh } from '@deephaven/jsapi-types'; | ||
import Log from '@deephaven/log'; | ||
import { assertNotNull } from '@deephaven/utils'; | ||
import { useEffect, useState } from 'react'; | ||
import { useObjectFetch } from './useObjectFetch'; | ||
|
||
const log = Log.module('useWidget'); | ||
|
||
/** | ||
* Wrapper object for a widget and error status. Both widget and error will be `null` if it is still loading. | ||
*/ | ||
type WidgetWrapper<T extends dh.Widget = dh.Widget> = { | ||
/** Widget object to retrieve */ | ||
widget: T | null; | ||
|
||
/** Error status if there was an issue fetching the widget */ | ||
error: unknown | null; | ||
}; | ||
|
||
/** | ||
* Retrieve a widget for the given variable descriptor. Note that if the widget is successfully fetched, ownership of the widget is passed to the consumer and will need to close the object as well. | ||
* @param descriptor Descriptor to get the widget for | ||
* @returns A WidgetWrapper object that contains the widget or an error status if there was an issue fetching the widget. Will contain nulls if still loading. | ||
*/ | ||
export function useWidget<T extends dh.Widget = dh.Widget>( | ||
descriptor: dh.ide.VariableDescriptor | ||
): WidgetWrapper<T> { | ||
const [wrapper, setWrapper] = useState<WidgetWrapper<T>>(() => ({ | ||
widget: null, | ||
error: null, | ||
})); | ||
|
||
const objectFetch = useObjectFetch<T>(descriptor); | ||
|
||
useEffect( | ||
function loadWidget() { | ||
log.debug('loadWidget', descriptor); | ||
|
||
const { fetch, error } = objectFetch; | ||
|
||
if (error != null) { | ||
// We can't fetch if there's an error getting the fetcher, just return an error | ||
setWrapper({ widget: null, error }); | ||
return; | ||
} | ||
|
||
if (fetch == null) { | ||
// Still loading | ||
setWrapper({ widget: null, error: null }); | ||
return; | ||
} | ||
|
||
let isCancelled = false; | ||
async function loadWidgetInternal() { | ||
try { | ||
assertNotNull(fetch); | ||
const newWidget = await fetch(); | ||
if (isCancelled) { | ||
log.debug2('loadWidgetInternal cancelled', descriptor, newWidget); | ||
newWidget.close(); | ||
newWidget.exportedObjects.forEach( | ||
(exportedObject: dh.WidgetExportedObject) => { | ||
exportedObject.close(); | ||
} | ||
); | ||
return; | ||
} | ||
log.debug('loadWidgetInternal done', descriptor, newWidget); | ||
|
||
setWrapper({ widget: newWidget, error: null }); | ||
} catch (e) { | ||
if (isCancelled) { | ||
return; | ||
} | ||
log.error('loadWidgetInternal error', descriptor, e); | ||
setWrapper({ widget: null, error: e }); | ||
} | ||
} | ||
loadWidgetInternal(); | ||
return () => { | ||
isCancelled = true; | ||
}; | ||
}, | ||
[descriptor, objectFetch] | ||
); | ||
|
||
return wrapper; | ||
} | ||
|
||
export default useWidget; |
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