diff --git a/ui/src/app/metadata/component/DeleteConfirmation.js b/ui/src/app/metadata/component/DeleteConfirmation.js
index 0df156370..a0f2f8c09 100644
--- a/ui/src/app/metadata/component/DeleteConfirmation.js
+++ b/ui/src/app/metadata/component/DeleteConfirmation.js
@@ -7,16 +7,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import Translate from '../../i18n/components/translate';
-export function DeleteConfirmation({ children, onConfirm, onCancel, body, title }) {
+export function DeleteConfirmation({ children, body, title }) {
const [deleting, setDeleting] = React.useState(false);
const ref = React.useRef();
const onConfirmClick = () => {
- if (onConfirm) {
- onConfirm();
- }
if (ref.current && typeof ref.current === 'function') {
ref.current();
}
@@ -24,9 +21,6 @@ export function DeleteConfirmation({ children, onConfirm, onCancel, body, title
}
const onCancelClick = () => {
- if (onCancel) {
- onCancel();
- }
setDeleting(false);
};
@@ -47,9 +41,9 @@ export function DeleteConfirmation({ children, onConfirm, onCancel, body, title
-
onCancelClick()}>
Cancel
diff --git a/ui/src/app/metadata/component/DeleteConfirmation.test.js b/ui/src/app/metadata/component/DeleteConfirmation.test.js
new file mode 100644
index 000000000..8aa4a57ee
--- /dev/null
+++ b/ui/src/app/metadata/component/DeleteConfirmation.test.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { act } from 'react-dom/test-utils';
+
+import { DeleteConfirmation } from './DeleteConfirmation';
+
+jest.mock('../../i18n/components/translate', () => {
+ return 'span';
+})
+
+const noop = jest.fn();
+
+let container;
+
+beforeEach(() => {
+ container = document.createElement('div');
+ document.body.appendChild(container);
+});
+
+afterEach(() => {
+ document.body.removeChild(container);
+ container = null;
+});
+
+test('Delete confirmation', () => {
+ act(() => {
+ ReactDOM.render(
+
+ {(block) => block(() => noop())}>}
+ ,
+ container);
+ });
+
+ const initiator = container.querySelector('button');
+
+ act(() => {
+ initiator.dispatchEvent(new MouseEvent('click', { bubbles: true }));
+ });
+
+ let modal = container.querySelector('.modal');
+ const confirm = container.querySelector('.btn-danger');
+
+ expect(modal).toBeDefined();
+ expect(confirm).toBeDefined();
+
+});
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
index 7afaca89b..948a43c39 100644
--- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
+++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js
@@ -174,9 +174,6 @@ export const HttpMetadataResolverAttributesSchema = {
options: DurationOptions,
'ui:placeholder': 'label.duration'
},
- proxyPort: {
- 'ui:widget': 'updown'
- },
httpClientRef: {
'ui:widget': 'hidden'
},
diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.test.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.test.js
new file mode 100644
index 000000000..d58cfe9b9
--- /dev/null
+++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.test.js
@@ -0,0 +1,160 @@
+import { BaseProviderDefinition } from './BaseProviderDefinition';
+import schema from '../../../../testing/dynamic-http.schema';
+const addErrorMockFn = jest.fn();
+
+const providers = [
+ {
+ resourceId: 1,
+ name: 'foo',
+ xmlId: 'bar'
+ },
+ {
+ resourceId: 2,
+ name: 'baz',
+ xmlId: 'xmlId'
+ }
+];
+
+const e = {
+ name: { addError: addErrorMockFn },
+ xmlId: { addError: addErrorMockFn }
+};
+
+describe('validator function', () => {
+ it('should validate against the providers', () => {
+ const validator = BaseProviderDefinition.validator(providers, 2);
+ const errors = validator({
+ name: 'foo',
+ xmlId: 'xmlId'
+ }, e);
+
+ expect(addErrorMockFn).toHaveBeenCalledTimes(2);
+ });
+
+ it('should check against the current id', () => {
+ const validator = BaseProviderDefinition.validator(providers, 1);
+ const errors = validator({
+ name: 'foo',
+ xmlId: 'naz'
+ }, e);
+
+ expect(addErrorMockFn).toHaveBeenCalledTimes(1);
+ });
+});
+
+describe('parser function', () => {
+
+ const base = {
+ metadataFilters: [
+ {
+ resourceId: 'foo',
+ name: 'foo',
+ '@type': 'NameIDFormat'
+ }
+ ]
+ };
+
+ const changes = {
+ metadataFilters: [
+ {
+ '@type': "RequiredValidUntil",
+ audId: 19,
+ createdBy: "root",
+ createdDate: "2021-06-11T13:29:18.384219",
+ current: false,
+ filterEnabled: false,
+ maxValidityInterval: "PT30S",
+ resourceId: "299feea8-3e2c-433e-bc2e-48d28bf84a8c"
+ }
+ ]
+ };
+
+ it('should validate against the providers', () => {
+ const parsed = BaseProviderDefinition.parser(
+ changes, base
+ );
+
+ expect(parsed.metadataFilters.length).toBe(2);
+ });
+});
+
+describe('formatter function', () => {
+
+ const md = {
+ metadataFilters: [
+ {
+ resourceId: 'foo',
+ name: 'foo',
+ '@type': 'NameIDFormat'
+ },
+ {
+ '@type': "RequiredValidUntil",
+ audId: 19,
+ createdBy: "root",
+ createdDate: "2021-06-11T13:29:18.384219",
+ current: false,
+ filterEnabled: false,
+ maxValidityInterval: "PT30S",
+ resourceId: "299feea8-3e2c-433e-bc2e-48d28bf84a8c"
+ }
+ ]
+ };
+
+ it('should validate against the providers', () => {
+ const parsed = BaseProviderDefinition.formatter(
+ md, schema
+ );
+
+ expect(parsed.metadataFilters.length).toBe(3);
+ });
+
+ it('should return no changes if no filters are on the provider', () => {
+ const parsed = BaseProviderDefinition.formatter(
+ {}, schema
+ );
+
+ expect(parsed.metadataFilters).toBeUndefined();
+ });
+
+ it('should return no changes if no provider is passed', () => {
+ const parsed = BaseProviderDefinition.formatter(
+ null, schema
+ );
+
+ expect(parsed).toBeNull();
+ });
+});
+
+describe('display function', () => {
+
+ const md = {
+ metadataFilters: [
+ {
+ resourceId: 'foo',
+ name: 'foo',
+ '@type': 'NameIDFormat'
+ },
+ {
+ '@type': "RequiredValidUntil",
+ audId: 19,
+ createdBy: "root",
+ createdDate: "2021-06-11T13:29:18.384219",
+ current: false,
+ filterEnabled: false,
+ maxValidityInterval: "PT30S",
+ resourceId: "299feea8-3e2c-433e-bc2e-48d28bf84a8c"
+ }
+ ]
+ };
+
+ it('should validate against the providers', () => {
+ const parsed = BaseProviderDefinition.display(md);
+ expect(Array.isArray(parsed)).toBe(false);
+ });
+
+ it('should return an unmodified provider if no filters are present', () => {
+ const local = {};
+ const parsed = BaseProviderDefinition.display(local);
+ expect(parsed).toEqual(local);
+ });
+});
\ No newline at end of file
diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.test.js b/ui/src/app/metadata/domain/source/SourceDefinition.test.js
new file mode 100644
index 000000000..130cb1110
--- /dev/null
+++ b/ui/src/app/metadata/domain/source/SourceDefinition.test.js
@@ -0,0 +1,15 @@
+import { SourceBase } from './SourceDefinition';
+
+describe('SourceDefinition', () => {
+ describe('parser', () => {
+ it('should remove null values', () => {
+ expect(SourceBase.parser({
+ foo: null,
+ bar: 'baz',
+ baz: {
+ bar: null
+ }
+ })).toEqual({bar: 'baz'});
+ });
+ })
+});
\ No newline at end of file
diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js
index e76b4e267..c38766404 100644
--- a/ui/src/app/metadata/editor/MetadataEditor.js
+++ b/ui/src/app/metadata/editor/MetadataEditor.js
@@ -35,7 +35,6 @@ export function MetadataEditor ({ current }) {
const { state, dispatch } = React.useContext(MetadataFormContext);
const { metadata, errors } = state;
-
const onChange = (changes) => {
dispatch(setFormDataAction(changes.formData));
dispatch(setFormErrorAction(changes.errors));
diff --git a/ui/src/testing/dynamic-http.schema.js b/ui/src/testing/dynamic-http.schema.js
new file mode 100644
index 000000000..29d68da8c
--- /dev/null
+++ b/ui/src/testing/dynamic-http.schema.js
@@ -0,0 +1,3 @@
+export const schema = { "type": "object", "required": ["name", "@type", "xmlId", "metadataRequestURLConstructionScheme"], "properties": { "name": { "title": "label.metadata-provider-name-dashboard-display-only", "description": "tooltip.metadata-provider-name-dashboard-display-only", "type": "string" }, "@type": { "title": "label.metadata-provider-type", "description": "tooltip.metadata-provider-type", "type": "string", "default": "DynamicHttpMetadataResolver" }, "enabled": { "title": "label.enable-provider-upon-saving", "description": "tooltip.enable-provider-upon-saving", "type": "boolean", "default": false }, "xmlId": { "title": "label.xml-id", "description": "tooltip.xml-id", "type": "string", "minLength": 1 }, "metadataRequestURLConstructionScheme": { "type": "object", "required": ["@type", "content"], "dependencies": { "@type": { "oneOf": [{ "properties": { "@type": { "enum": ["Regex"] }, "match": { "title": "label.match", "description": "tooltip.match", "type": "string", "widget": { "id": "string", "required": true } } }, "required": ["@type", "content", "match"] }, { "properties": { "@type": { "enum": ["MetadataQueryProtocol"] } }, "required": ["@type", "content"] }] } }, "properties": { "@type": { "title": "label.md-request-type", "description": "tooltip.md-request-type", "type": "string", "widget": { "id": "select" }, "enum": ["MetadataQueryProtocol", "Regex"] }, "content": { "title": "label.md-request-value", "description": "tooltip.md-request-value", "type": "string" } } }, "requireValidMetadata": { "title": "label.require-valid-metadata", "description": "tooltip.require-valid-metadata", "type": "boolean" }, "failFastInitialization": { "title": "label.fail-fast-init", "description": "tooltip.fail-fast-init", "type": "boolean" }, "dynamicMetadataResolverAttributes": { "type": "object", "dependencies": { "initializeFromPersistentCacheInBackground": { "oneOf": [{ "properties": { "initializeFromPersistentCacheInBackground": { "enum": [true] }, "backgroundInitializationFromCacheDelay": { "title": "label.background-init-from-cache-delay", "description": "tooltip.background-init-from-cache-delay", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" } } }, { "properties": { "initializeFromPersistentCacheInBackground": { "enum": [false] } } }] } }, "properties": { "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", "type": "number", "multipleOf": 0.01, "minimum": 0.001, "maximum": 0.999 }, "minCacheDuration": { "title": "label.min-cache-duration", "description": "tooltip.min-cache-duration", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "maxCacheDuration": { "title": "label.max-cache-duration", "description": "tooltip.max-cache-duration", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "maxIdleEntityData": { "title": "label.max-idle-entity-data", "description": "tooltip.max-idle-entity-data", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "removeIdleEntityData": { "title": "label.remove-idle-entity-data", "description": "tooltip.remove-idle-entity-data", "type": "boolean" }, "cleanupTaskInterval": { "title": "label.cleanup-task-interval", "description": "tooltip.cleanup-task-interval", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "persistentCacheManagerDirectory": { "title": "label.persistent-cache-manager-directory", "description": "tooltip.persistent-cache-manager-directory", "type": "string", "minLength": 1 }, "initializeFromPersistentCacheInBackground": { "title": "label.initialize-from-persistent-cache-in-background", "description": "tooltip.initialize-from-persistent-cache-in-background", "type": "boolean" } } }, "httpMetadataResolverAttributes": { "order": [], "type": "object", "fieldsets": [{ "title": "label.http-security-attributes", "type": "section", "class": "col-12", "fields": ["disregardTLSCertificate"] }, { "title": "label.http-connection-attributes", "type": "section", "fields": ["connectionRequestTimeout", "connectionTimeout", "socketTimeout"] }, { "title": "label.http-proxy-attributes", "type": "section", "class": "col-12", "fields": ["proxyHost", "proxyPort", "proxyUser", "proxyPassword"] }, { "title": "label.http-caching-attributes", "type": "section", "class": "col-12", "fields": ["httpCaching", "httpCacheDirectory", "httpMaxCacheEntries", "httpMaxCacheEntrySize"] }, { "title": "", "type": "hidden", "class": "col-12", "fields": ["tlsTrustEngineRef", "httpClientSecurityParametersRef", "httpClientRef"] }], "properties": { "disregardTLSCertificate": { "type": "boolean", "title": "label.disregard-tls-cert", "description": "tooltip.disregard-tls-cert" }, "httpClientRef": { "type": "string", "title": "", "description": "", "widget": "hidden" }, "connectionRequestTimeout": { "type": "string", "title": "label.connection-request-timeout", "description": "tooltip.connection-request-timeout", "widget": { "id": "datalist", "data": ["PT0S", "PT30S", "PT1M", "PT10M", "PT30M", "PT1H", "PT4H", "PT12H", "PT24H"] }, "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "connectionTimeout": { "type": "string", "title": "label.connection-timeout", "description": "tooltip.connection-timeout", "widget": { "id": "datalist", "data": ["PT0S", "PT30S", "PT1M", "PT10M", "PT30M", "PT1H", "PT4H", "PT12H", "PT24H"] }, "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "socketTimeout": { "type": "string", "title": "label.socket-timeout", "description": "tooltip.socket-timeout", "widget": { "id": "datalist", "data": ["PT0S", "PT30S", "PT1M", "PT10M", "PT30M", "PT1H", "PT4H", "PT12H", "PT24H"] }, "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" }, "tlsTrustEngineRef": { "type": "string", "title": "", "description": "" }, "httpClientSecurityParametersRef": { "type": "string", "title": "", "description": "" }, "proxyHost": { "type": "string", "title": "label.proxy-host", "description": "tooltip.proxy-host" }, "proxyPort": { "type": "integer", "title": "label.proxy-port", "description": "tooltip.proxy-port" }, "proxyUser": { "type": "string", "title": "label.proxy-user", "description": "tooltip.proxy-user" }, "proxyPassword": { "type": "string", "title": "label.proxy-password", "description": "tooltip.proxy-password" }, "httpCaching": { "type": "string", "title": "label.http-caching", "description": "tooltip.http-caching", "widget": { "id": "select" }, "oneOf": [{ "enum": ["none"], "description": "value.none" }, { "enum": ["file"], "description": "value.file" }, { "enum": ["memory"], "description": "value.memory" }] }, "httpCacheDirectory": { "type": "string", "title": "label.http-caching-directory", "description": "tooltip.http-caching-directory" }, "httpMaxCacheEntries": { "type": "integer", "title": "label.http-max-cache-entries", "description": "tooltip.http-max-cache-entries", "minimum": 0 }, "httpMaxCacheEntrySize": { "type": "integer", "title": "label.max-cache-entry-size", "description": "tooltip.max-cache-entry-size", "minimum": 0 } } }, "metadataFilters": { "$id": "metadataFilters", "title": "", "description": "", "type": "array", "items": [{ "$id": "RequiredValidUntil", "title": "label.required-valid-until", "type": "object", "widget": { "id": "fieldset" }, "properties": { "@type": { "type": "string", "default": "RequiredValidUntil" }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", "type": "string", "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" } } }, { "$id": "SignatureValidation", "title": "label.signature-validation-filter", "type": "object", "widget": { "id": "fieldset" }, "properties": { "@type": { "type": "string", "default": "SignatureValidation" }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", "type": "boolean" }, "certificateFile": { "title": "label.certificate-file", "description": "tooltip.certificate-file", "type": "string" } }, "dependencies": { "requireSignedRoot": { "oneOf": [{ "properties": { "requireSignedRoot": { "enum": [true] } }, "required": ["certificateFile"] }, { "properties": { "requireSignedRoot": { "enum": [false] } } }] } } }, { "$id": "EntityRoleWhiteList", "title": "label.entity-role-whitelist", "type": "object", "widget": { "id": "fieldset" }, "properties": { "@type": { "type": "string", "default": "EntityRoleWhiteList" }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", "type": "array", "items": { "widget": { "id": "select" }, "type": "string", "enum": ["SPSSODescriptor", "AttributeAuthorityDescriptor"], "enumNames": ["value.spdescriptor", "value.attr-auth-descriptor"] } }, "removeRolelessEntityDescriptors": { "title": "label.remove-roleless-entity-descriptors", "description": "tooltip.remove-roleless-entity-descriptors", "type": "boolean" }, "removeEmptyEntitiesDescriptors": { "title": "label.remove-empty-entities-descriptors", "description": "tooltip.remove-empty-entities-descriptors", "type": "boolean" } } }] } } };
+
+export default schema;
\ No newline at end of file
diff --git a/ui/yarn.lock b/ui/yarn.lock
index dcbc3d1c9..36cf32e13 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -9400,6 +9400,11 @@ react-infinite-scroll-component@^6.1.0:
dependencies:
throttle-debounce "^2.1.0"
+"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -9564,6 +9569,24 @@ react-scroll@^1.8.2:
lodash.throttle "^4.1.1"
prop-types "^15.7.2"
+react-shallow-renderer@^16.13.1:
+ version "16.14.1"
+ resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
+ integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
+ dependencies:
+ object-assign "^4.1.1"
+ react-is "^16.12.0 || ^17.0.0"
+
+react-test-renderer@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
+ integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
+ dependencies:
+ object-assign "^4.1.1"
+ react-is "^17.0.2"
+ react-shallow-renderer "^16.13.1"
+ scheduler "^0.20.2"
+
react-transition-group@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"