- {cloneElement(
- tabs,
- {
- // The location pathname will contain the page path including the current tab path
- // so we can use it as a way to determine the current tab
- value: location.pathname,
- match,
- },
- children
- )}
+ {cloneElement(tabs, {}, children)}
@@ -140,4 +127,4 @@ TabbedShowLayout.defaultProps = {
tabs:
,
};
-export default withRouter(TabbedShowLayout);
+export default TabbedShowLayout;
diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayoutTabs.js b/packages/ra-ui-materialui/src/detail/TabbedShowLayoutTabs.js
index 639164691be..8639209962d 100644
--- a/packages/ra-ui-materialui/src/detail/TabbedShowLayoutTabs.js
+++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayoutTabs.js
@@ -1,34 +1,42 @@
import React, { Children, cloneElement, isValidElement } from 'react';
import PropTypes from 'prop-types';
import Tabs from '@material-ui/core/Tabs';
+import { useLocation, useRouteMatch } from 'react-router';
-const getTabFullPath = (tab, index, baseUrl) =>
+export const getTabFullPath = (tab, index, baseUrl) =>
`${baseUrl}${
tab.props.path ? `/${tab.props.path}` : index > 0 ? `/${index}` : ''
}`;
-const TabbedShowLayoutTabs = ({ children, match, value, ...rest }) => (
-
- {Children.map(children, (tab, index) => {
- if (!tab || !isValidElement(tab)) return null;
- // Builds the full tab tab which is the concatenation of the last matched route in the
- // TabbedShowLayout hierarchy (ex: '/posts/create', '/posts/12', , '/posts/12/show')
- // and the tab path.
- // This will be used as the Tab's value
- const tabPath = getTabFullPath(tab, index, match.url);
+const TabbedShowLayoutTabs = ({ children, ...rest }) => {
+ const location = useLocation();
+ const match = useRouteMatch();
- return cloneElement(tab, {
- context: 'header',
- value: tabPath,
- });
- })}
-
-);
+ // The location pathname will contain the page path including the current tab path
+ // so we can use it as a way to determine the current tab
+ const value = location.pathname;
+
+ return (
+
+ {Children.map(children, (tab, index) => {
+ if (!tab || !isValidElement(tab)) return null;
+ // Builds the full tab tab which is the concatenation of the last matched route in the
+ // TabbedShowLayout hierarchy (ex: '/posts/create', '/posts/12', , '/posts/12/show')
+ // and the tab path.
+ // This will be used as the Tab's value
+ const tabPath = getTabFullPath(tab, index, match.url);
+
+ return cloneElement(tab, {
+ context: 'header',
+ value: tabPath,
+ });
+ })}
+
+ );
+};
TabbedShowLayoutTabs.propTypes = {
children: PropTypes.node,
- match: PropTypes.object,
- value: PropTypes.string,
};
export default TabbedShowLayoutTabs;
diff --git a/packages/ra-ui-materialui/src/field/ArrayField.spec.js b/packages/ra-ui-materialui/src/field/ArrayField.spec.js
index 8cabc742437..1534945bc2f 100644
--- a/packages/ra-ui-materialui/src/field/ArrayField.spec.js
+++ b/packages/ra-ui-materialui/src/field/ArrayField.spec.js
@@ -1,92 +1,60 @@
import React from 'react';
-import { shallow, mount } from 'enzyme';
-import { createStore } from 'redux';
-import { Provider } from 'react-redux';
+import { render, cleanup } from '@testing-library/react';
import { ArrayField } from './ArrayField';
import NumberField from './NumberField';
import TextField from './TextField';
import Datagrid from '../list/Datagrid';
+import { TestContext } from 'ra-core';
describe('
', () => {
+ afterEach(cleanup);
+
+ const DummyIterator = props => (
+
+
+
+
+ );
+
it('should not fail for empty records', () => {
- const IteratorMock = jest.fn();
- shallow(
-
-
-
- ).dive();
- expect(IteratorMock.mock.calls.length).toBe(1);
- expect(IteratorMock.mock.calls[0][0]).toMatchObject({
- data: {},
- ids: [],
- });
- });
+ const { queryByText } = render(
+
+
+
+
+
+ );
- it('should pass the embedded array as data and ids props to child', () => {
- const IteratorMock = jest.fn();
- shallow(
-
-
-
- ).dive();
- expect(IteratorMock.mock.calls.length).toBe(1);
- expect(IteratorMock.mock.calls[0][0]).toMatchObject({
- data: {
- '{"id":123,"foo":"bar"}': { foo: 'bar', id: 123 },
- '{"id":456,"foo":"baz"}': { foo: 'baz', id: 456 },
- },
- ids: ['{"id":123,"foo":"bar"}', '{"id":456,"foo":"baz"}'],
- });
+ // Test the datagrid know about the fields
+ expect(queryByText('resources.posts.fields.id')).not.toBeNull();
+ expect(queryByText('resources.posts.fields.foo')).not.toBeNull();
});
it('should render the underlying iterator component', () => {
- const Dummy = () => (
-
-
-
-
-
-
+ const { queryByText } = render(
+
+
+
+
+
);
- const wrapper = mount(
-
({}))}>
-
-
- );
- expect(
- wrapper
- .find('DatagridRow TextField span')
- .at(0)
- .text()
- ).toBe('bar');
- expect(
- wrapper
- .find('DatagridRow NumberField span')
- .at(0)
- .text()
- ).toBe('123');
- expect(
- wrapper
- .find('DatagridRow TextField span')
- .at(1)
- .text()
- ).toBe('baz');
- expect(
- wrapper
- .find('DatagridRow NumberField span')
- .at(1)
- .text()
- ).toBe('456');
+
+ // Test the datagrid know about the fields
+ expect(queryByText('resources.posts.fields.id')).not.toBeNull();
+ expect(queryByText('resources.posts.fields.foo')).not.toBeNull();
+
+ // Test the fields values
+ expect(queryByText('bar')).not.toBeNull();
+ expect(queryByText('123')).not.toBeNull();
+
+ expect(queryByText('baz')).not.toBeNull();
+ expect(queryByText('456')).not.toBeNull();
});
});
diff --git a/packages/ra-ui-materialui/src/form/SimpleForm.js b/packages/ra-ui-materialui/src/form/SimpleForm.js
index 326a31daaa6..9ae37e8c6ea 100644
--- a/packages/ra-ui-materialui/src/form/SimpleForm.js
+++ b/packages/ra-ui-materialui/src/form/SimpleForm.js
@@ -2,7 +2,6 @@ import React, { Children, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import { Form } from 'react-final-form';
import arrayMutators from 'final-form-arrays';
-import { useSelector } from 'react-redux';
import classnames from 'classnames';
import { useTranslate, useInitializeFormWithRecord } from 'ra-core';
@@ -10,7 +9,7 @@ import FormInput from './FormInput';
import Toolbar from './Toolbar';
import CardContentInner from '../layout/CardContentInner';
-const SimpleForm = ({ initialValues, ...props }) => {
+const SimpleForm = ({ initialValues, saving, ...props }) => {
let redirect = useRef(props.redirect);
// We don't use state here for two reasons:
// 1. There no way to execute code only after the state has been updated
@@ -19,7 +18,6 @@ const SimpleForm = ({ initialValues, ...props }) => {
redirect.current = newRedirect;
};
- const saving = useSelector(state => state.admin.saving);
const translate = useTranslate();
const submit = values => {
const finalRedirect =
diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.js b/packages/ra-ui-materialui/src/form/TabbedForm.js
index f782162d3a3..1f976e5eae7 100644
--- a/packages/ra-ui-materialui/src/form/TabbedForm.js
+++ b/packages/ra-ui-materialui/src/form/TabbedForm.js
@@ -3,14 +3,14 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Form } from 'react-final-form';
import arrayMutators from 'final-form-arrays';
-import { useSelector } from 'react-redux';
-import { withRouter, Route } from 'react-router-dom';
+import { Route } from 'react-router-dom';
import Divider from '@material-ui/core/Divider';
import { makeStyles } from '@material-ui/core/styles';
import { useTranslate, useInitializeFormWithRecord } from 'ra-core';
import Toolbar from './Toolbar';
-import TabbedFormTabs from './TabbedFormTabs';
+import TabbedFormTabs, { getTabFullPath } from './TabbedFormTabs';
+import { useRouteMatch, useLocation } from 'react-router';
const useStyles = makeStyles(theme => ({
errorTabButton: { color: theme.palette.error.main },
@@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({
},
}));
-const TabbedForm = ({ initialValues, ...props }) => {
+const TabbedForm = ({ initialValues, saving, ...props }) => {
let redirect = useRef(props.redirect);
// We don't use state here for two reasons:
// 1. There no way to execute code only after the state has been updated
@@ -29,7 +29,7 @@ const TabbedForm = ({ initialValues, ...props }) => {
const setRedirect = newRedirect => {
redirect.current = newRedirect;
};
- const saving = useSelector(state => state.admin.saving);
+
const translate = useTranslate();
const classes = useStyles();
@@ -75,7 +75,7 @@ const defaultSubscription = {
invalid: true,
};
-export default withRouter(TabbedForm);
+export default TabbedForm;
export const TabbedFormView = ({
basePath,
@@ -85,8 +85,6 @@ export const TabbedFormView = ({
form,
handleSubmit,
invalid,
- location,
- match,
pristine,
record,
redirect: defaultRedirect,
@@ -116,6 +114,9 @@ export const TabbedFormView = ({
const tabsWithErrors = findTabsWithErrors(children, form.getState().errors);
+ const match = useRouteMatch();
+ const location = useLocation();
+
const url = match ? match.url : location.pathname;
return (