feat(filters): save worklist query filters to session storage so that they persist between navigation to the viewer and back (#3749)
Co-authored-by: ladeirarodolfo <39910206+ladeirarodolfo@users.noreply.github.com>
This commit is contained in:
parent
db395852b6
commit
2a15ef0e44
@ -3,6 +3,7 @@
|
||||
describe('OHIF Study List', function () {
|
||||
context('Desktop resolution', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => win.sessionStorage.clear());
|
||||
cy.openStudyList();
|
||||
|
||||
cy.viewport(1750, 720);
|
||||
@ -14,6 +15,10 @@ describe('OHIF Study List', function () {
|
||||
cy.get('@StudyDescription').clear();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
cy.window().then(win => win.sessionStorage.clear());
|
||||
});
|
||||
|
||||
it('Displays several studies initially', function () {
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
@ -33,6 +38,21 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('maintains Patient Name filter upon return from viewer', function () {
|
||||
cy.get('@PatientName').type('Juno');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
|
||||
cy.get(
|
||||
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
|
||||
).click();
|
||||
cy.get('[data-cy="return-to-work-list"]').click();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches MRN with exact string', function () {
|
||||
cy.get('@MRN').type('0000003');
|
||||
//Wait result list to be displayed
|
||||
@ -43,6 +63,21 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('maintains MRN filter upon return from viewer', function () {
|
||||
cy.get('@MRN').type('0000003');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
|
||||
cy.get(
|
||||
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
|
||||
).click();
|
||||
cy.get('[data-cy="return-to-work-list"]').click();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000003');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function () {
|
||||
cy.get('@AccessionNumber').type('321');
|
||||
//Wait result list to be displayed
|
||||
@ -53,6 +88,21 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('maintains Accession filter upon return from viewer', function () {
|
||||
cy.get('@AccessionNumber').type('0000155811');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
|
||||
cy.get(
|
||||
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
|
||||
).click();
|
||||
cy.get('[data-cy="return-to-work-list"]').click();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000155811');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Description with exact string', function () {
|
||||
cy.get('@StudyDescription').type('PETCT');
|
||||
//Wait result list to be displayed
|
||||
@ -63,6 +113,21 @@ describe('OHIF Study List', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('maintains Description filter upon return from viewer', function () {
|
||||
cy.get('@StudyDescription').type('PETCT');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('[data-cy="studyRow-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]').click();
|
||||
cy.get(
|
||||
'[data-cy="mode-basic-test-1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1"]'
|
||||
).click();
|
||||
cy.get('[data-cy="return-to-work-list"]').click();
|
||||
cy.get('@searchResult2').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('PETCT');
|
||||
});
|
||||
});
|
||||
|
||||
/* Todo: fix react select
|
||||
it('searches Modality with camel case', function() {
|
||||
cy.get('@modalities').type('Ct');
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
AboutModal,
|
||||
UserPreferences,
|
||||
LoadingIndicatorProgress,
|
||||
useSessionStorage,
|
||||
} from '@ohif/ui';
|
||||
|
||||
import i18n from '@ohif/i18n';
|
||||
@ -60,9 +61,17 @@ function WorkList({
|
||||
const navigate = useNavigate();
|
||||
const STUDIES_LIMIT = 101;
|
||||
const queryFilterValues = _getQueryFilterValues(searchParams);
|
||||
const [sessionQueryFilterValues, updateSessionQueryFilterValues] = useSessionStorage({
|
||||
key: 'queryFilterValues',
|
||||
defaultValue: queryFilterValues,
|
||||
// ToDo: useSessionStorage currently uses an unload listener to clear the filters from session storage
|
||||
// so on systems that do not support unload events a user will NOT be able to alter any existing filter
|
||||
// in the URL, load the page and have it apply.
|
||||
clearOnUnload: true,
|
||||
});
|
||||
const [filterValues, _setFilterValues] = useState({
|
||||
...defaultFilterValues,
|
||||
...queryFilterValues,
|
||||
...sessionQueryFilterValues,
|
||||
});
|
||||
|
||||
const debouncedFilterValues = useDebounce(filterValues, 200);
|
||||
@ -119,6 +128,7 @@ function WorkList({
|
||||
val.pageNumber = 1;
|
||||
}
|
||||
_setFilterValues(val);
|
||||
updateSessionQueryFilterValues(val);
|
||||
setExpandedRows([]);
|
||||
};
|
||||
|
||||
@ -251,6 +261,7 @@ function WorkList({
|
||||
moment(time, ['HH', 'HHmm', 'HHmmss', 'HHmmss.SSS']).format('hh:mm A');
|
||||
|
||||
return {
|
||||
dataCY: `studyRow-${studyInstanceUid}`,
|
||||
row: [
|
||||
{
|
||||
key: 'patientName',
|
||||
@ -377,6 +388,7 @@ function WorkList({
|
||||
disabled={!isValidMode}
|
||||
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
|
||||
onClick={() => {}}
|
||||
data-cy={`mode-${mode.routeName}-${studyInstanceUid}`}
|
||||
>
|
||||
{t(`Modes:${mode.displayName}`)}
|
||||
</LegacyButton>
|
||||
|
||||
13
platform/ui/jest.config.js
Normal file
13
platform/ui/jest.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
const base = require('../../jest.config.base.js');
|
||||
const pkg = require('./package');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
name: pkg.name,
|
||||
displayName: pkg.name,
|
||||
// rootDir: "../.."
|
||||
// testMatch: [
|
||||
// //`<rootDir>/platform/${pack.name}/**/*.spec.js`
|
||||
// "<rootDir>/platform/app/**/*.test.js"
|
||||
// ]
|
||||
};
|
||||
@ -50,7 +50,9 @@
|
||||
"react-window": "^1.8.9",
|
||||
"react-with-direction": "^1.3.1",
|
||||
"swiper": "^8.4.2",
|
||||
"webpack": "^5.81.0"
|
||||
"webpack": "^5.81.0",
|
||||
"@testing-library/react-hooks": "^3.2.1",
|
||||
"react-test-renderer": "^16.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.4",
|
||||
|
||||
@ -175,7 +175,7 @@ DateRange.propTypes = {
|
||||
/** YYYYMMDD (19921022) */
|
||||
startDate: PropTypes.string,
|
||||
/** YYYYMMDD (19921022) */
|
||||
endDate: PropTypes.object,
|
||||
endDate: PropTypes.string,
|
||||
/** Callback that received { startDate: string(YYYYMMDD), endDate: string(YYYYMMDD)} */
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -43,6 +43,7 @@ function Header({
|
||||
isReturnEnabled && 'cursor-pointer'
|
||||
)}
|
||||
onClick={onClickReturn}
|
||||
data-cy="return-to-work-list"
|
||||
>
|
||||
{isReturnEnabled && (
|
||||
<Icon
|
||||
|
||||
@ -52,7 +52,7 @@ const StudyListFilter = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="flex h-[34px] flex-row items-center">
|
||||
{/* TODO revisit the completely rounded style of button used for clearing the study list filter - for now use LegacyButton*/}
|
||||
{isFiltering && (
|
||||
<LegacyButton
|
||||
@ -76,7 +76,7 @@ const StudyListFilter = ({
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-primary-light self-end pb-1"
|
||||
className="text-primary-light"
|
||||
>
|
||||
{t('Studies')}
|
||||
</Typography>
|
||||
|
||||
@ -7,10 +7,13 @@ import Icon from '../Icon';
|
||||
|
||||
const StudyListTableRow = props => {
|
||||
const { tableData } = props;
|
||||
const { row, expandedContent, onClickRow, isExpanded } = tableData;
|
||||
const { row, expandedContent, onClickRow, isExpanded, dataCY } = tableData;
|
||||
return (
|
||||
<>
|
||||
<tr className="select-none">
|
||||
<tr
|
||||
className="select-none"
|
||||
data-cy={dataCY}
|
||||
>
|
||||
<td
|
||||
className={classnames('border-0 p-0', {
|
||||
'border-secondary-light bg-primary-dark border-b': isExpanded,
|
||||
@ -104,6 +107,7 @@ StudyListTableRow.propTypes = {
|
||||
expandedContent: PropTypes.node.isRequired,
|
||||
onClickRow: PropTypes.func.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
dataCY: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import useResizeObserver from './useResizeObserver';
|
||||
import useSessionStorage from './useSessionStorage';
|
||||
|
||||
export { useResizeObserver };
|
||||
export { useResizeObserver, useSessionStorage };
|
||||
|
||||
102
platform/ui/src/hooks/useSessionStorage.test.js
Normal file
102
platform/ui/src/hooks/useSessionStorage.test.js
Normal file
@ -0,0 +1,102 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import useSessionStorage from './useSessionStorage';
|
||||
|
||||
const SESSION_STORAGE_KEY = 'test';
|
||||
|
||||
describe('Hook Session Storage', () => {
|
||||
beforeEach(() => {
|
||||
window.sessionStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('hook should return state and setState', () => {
|
||||
const data = { test: 1 };
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
expect(hookState).toStrictEqual(data);
|
||||
expect(typeof setHookState).toBe('function');
|
||||
});
|
||||
|
||||
it('hook should store data on sessionStorage', () => {
|
||||
const data = { test: 2 };
|
||||
renderHook(() => useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data }));
|
||||
|
||||
const dataStr = JSON.stringify(data);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
expect(dataSessionStorage).toEqual(dataStr);
|
||||
});
|
||||
|
||||
it('hook should return stored data from sessionStorage', () => {
|
||||
const data = { test: 3 };
|
||||
const dataToCompare = { test: 4 };
|
||||
|
||||
window.sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(dataToCompare));
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
expect(hookState).toStrictEqual(dataToCompare);
|
||||
});
|
||||
|
||||
it('hook should provide a setState method which updates its state', () => {
|
||||
const data = { test: 5 };
|
||||
const dataToCompare = { test: 6 };
|
||||
const { result } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
act(() => {
|
||||
setHookState(dataToCompare);
|
||||
});
|
||||
|
||||
const dataToCompareStr = JSON.stringify(dataToCompare);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
const [hookStateToCompare] = result.current;
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookStateToCompare).toStrictEqual(dataToCompare);
|
||||
});
|
||||
|
||||
it('hook state must be preserved in case rerender', () => {
|
||||
const data = { test: 7 };
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
|
||||
rerender();
|
||||
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
const dataToCompareStr = JSON.stringify(data);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookState).toStrictEqual(data);
|
||||
});
|
||||
|
||||
it('hook state must be preserved in case multiple operations and rerender', () => {
|
||||
const data = { test: 8 };
|
||||
const dataToCompare = { test: 9 };
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useSessionStorage({ key: SESSION_STORAGE_KEY, defaultValue: data })
|
||||
);
|
||||
const [hookState, setHookState] = result.current;
|
||||
|
||||
act(() => {
|
||||
setHookState(dataToCompare);
|
||||
});
|
||||
|
||||
rerender();
|
||||
|
||||
const dataToCompareStr = JSON.stringify(dataToCompare);
|
||||
const dataSessionStorage = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
||||
|
||||
const [hookStateToCompare] = result.current;
|
||||
expect(dataSessionStorage).toEqual(dataToCompareStr);
|
||||
expect(hookStateToCompare).toStrictEqual(dataToCompare);
|
||||
});
|
||||
});
|
||||
71
platform/ui/src/hooks/useSessionStorage.tsx
Normal file
71
platform/ui/src/hooks/useSessionStorage.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* A map of session storage items that should be cleared out of session storage
|
||||
* when the page unloads.
|
||||
*/
|
||||
const sessionItemsToClearOnUnload: Map<string, string> = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* This callback simulates clearing the various session items when a page unloads.
|
||||
* When the page is hidden the session storage items are removed but maintained
|
||||
* in the map above in case the page becomes visible again. So those pages that
|
||||
* are hidden because they are being unloaded have their session storage disposed
|
||||
* of for ever. For those pages that are hidden, but later return to visible,
|
||||
* this callback restores the session storage from the map above.
|
||||
*/
|
||||
const visibilityChangeCallback = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
Array.from(sessionItemsToClearOnUnload.keys()).forEach(key => {
|
||||
window.sessionStorage.removeItem(key);
|
||||
});
|
||||
} else {
|
||||
Array.from(sessionItemsToClearOnUnload.keys()).forEach(key => {
|
||||
window.sessionStorage.setItem(key, sessionItemsToClearOnUnload.get(key));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Technically there is no memory leak here because the listener needs to
|
||||
* persist until the page unloads and once the page unloads it will be gone.
|
||||
*/
|
||||
document.addEventListener('visibilitychange', visibilityChangeCallback);
|
||||
|
||||
type useSessionStorageProps = {
|
||||
key: string;
|
||||
defaultValue: unknown;
|
||||
clearOnUnload: boolean;
|
||||
};
|
||||
|
||||
const useSessionStorage = ({
|
||||
key,
|
||||
defaultValue = {},
|
||||
clearOnUnload = false,
|
||||
}: useSessionStorageProps) => {
|
||||
const valueFromStorage = window.sessionStorage.getItem(key);
|
||||
const storageValue = valueFromStorage ? JSON.parse(valueFromStorage) : defaultValue;
|
||||
const [sessionItem, setSessionItem] = useState({ ...storageValue });
|
||||
|
||||
const updateSessionItem = useCallback(value => {
|
||||
setSessionItem({ ...value });
|
||||
|
||||
const valueAsStr = JSON.stringify(value);
|
||||
|
||||
if (!clearOnUnload || document.visibilityState === 'visible') {
|
||||
window.sessionStorage.setItem(key, valueAsStr);
|
||||
}
|
||||
|
||||
if (clearOnUnload) {
|
||||
sessionItemsToClearOnUnload.set(key, valueAsStr);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateSessionItem(sessionItem);
|
||||
}, []);
|
||||
|
||||
return [sessionItem, updateSessionItem];
|
||||
};
|
||||
|
||||
export default useSessionStorage;
|
||||
@ -119,6 +119,8 @@ export {
|
||||
ViewportOverlay,
|
||||
} from './components';
|
||||
|
||||
export { useSessionStorage } from './hooks';
|
||||
|
||||
/** These are mostly used in the docs */
|
||||
export { getIcon, ICONS, addIcon } from './components/Icon/getIcon';
|
||||
export { BackgroundColor } from './pages/Colors/BackgroundColor';
|
||||
|
||||
42
yarn.lock
42
yarn.lock
@ -4860,6 +4860,14 @@
|
||||
dependencies:
|
||||
defer-to-connect "^2.0.1"
|
||||
|
||||
"@testing-library/react-hooks@^3.2.1":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.7.0.tgz#6d75c5255ef49bce39b6465bf6b49e2dac84919e"
|
||||
integrity sha512-TwfbY6BWtWIHitjT05sbllyLIProcysC0dF0q1bbDa7OHLC6A6rJOYJwZ13hzfz3O4RtOuInmprBozJRyyo7/g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@types/testing-library__react-hooks" "^3.4.0"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
@ -5322,6 +5330,13 @@
|
||||
"@types/history" "^4.7.11"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-test-renderer@*":
|
||||
version "18.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.5.tgz#b67a6ff37acd93d1b971ec4c838f69d52e772db0"
|
||||
integrity sha512-PsnmF4Hpi61PTRX+dTxkjgDdtZ09kFFgPXczoF+yBfOVxn7xBLPvKP1BUrSasYHmerj33rhoJuvpIMsJuyRqHw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@^4.4.0":
|
||||
version "4.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
|
||||
@ -5437,6 +5452,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310"
|
||||
integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==
|
||||
|
||||
"@types/testing-library__react-hooks@^3.4.0":
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.1.tgz#b8d7311c6c1f7db3103e94095fe901f8fef6e433"
|
||||
integrity sha512-G4JdzEcq61fUyV6wVW9ebHWEiLK2iQvaBuCHHn9eMSbZzVh4Z4wHnUGIvQOYCCYeu5DnUtFyNYuAAgbSaO/43Q==
|
||||
dependencies:
|
||||
"@types/react-test-renderer" "*"
|
||||
|
||||
"@types/tough-cookie@*":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
|
||||
@ -17331,7 +17353,7 @@ react-is@18.1.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67"
|
||||
integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4:
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4, react-is@^16.8.6:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@ -17528,6 +17550,16 @@ react-style-singleton@^2.2.1:
|
||||
invariant "^2.2.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-test-renderer@^16.12.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
|
||||
integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.8.6"
|
||||
scheduler "^0.19.1"
|
||||
|
||||
react-textarea-autosize@^8.3.2:
|
||||
version "8.5.2"
|
||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.2.tgz#6421df2b5b50b9ca8c5e96fd31be688ea7fa2f9d"
|
||||
@ -18323,6 +18355,14 @@ saxes@^6.0.0:
|
||||
dependencies:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
scheduler@^0.19.1:
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
|
||||
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
scheduler@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user