Skip to content

Commit 0988e12

Browse files
authored
feat(guesser): migrate to full TS (#425)
1 parent d55e3d3 commit 0988e12

29 files changed

+579
-305
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ end_of_line = LF
55
trim_trailing_whitespace = true
66
insert_final_newline = true
77

8-
[*.js]
8+
[*.{js,ts,tsx}]
99
indent_style = space
1010
charset = utf-8
1111
indent_size = 2

.eslintrc.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,6 @@ module.exports = {
241241
'jsx-a11y/img-redundant-alt': 'warn',
242242
'jsx-a11y/no-access-key': 'warn',
243243

244-
// TODO: update these rules
245-
'@typescript-eslint/no-explicit-any': 'off',
246-
'@typescript-eslint/ban-ts-comment': 'off',
247-
'@typescript-eslint/no-empty-function': 'off',
248244
'@typescript-eslint/no-unused-vars': [
249245
'warn',
250246
{ ignoreRestSiblings: true, argsIgnorePattern: '^_' },

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
},
2626
"devDependencies": {
2727
"@testing-library/react": "^12.1.0",
28+
"@types/history": "^4.7.0",
2829
"@types/jest": "^27.4.0",
2930
"@types/jsonld": "^1.5.0",
3031
"@types/lodash.isplainobject": "^4.0.0",
32+
"@types/react-test-renderer": "^17.0.0",
3133
"@typescript-eslint/eslint-plugin": "^5.8.0",
3234
"@typescript-eslint/parser": "^5.8.0",
3335
"eslint": "^7.14.0",

src/AdminGuesser.test.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
import React from 'react';
2-
import ShallowRenderer from 'react-test-renderer/shallow';
2+
import { createRenderer } from 'react-test-renderer/shallow';
33
import { AdminResourcesGuesser } from './AdminGuesser';
44
import ResourceGuesser from './ResourceGuesser';
5-
import { DataProvider } from 'react-admin';
65
import resources from './__fixtures__/resources';
6+
import { API_DATA } from './__fixtures__/parsedData';
7+
import { ApiPlatformAdminDataProvider, ApiPlatformAdminRecord } from './types';
78

8-
const dataProvider = {
9+
const dataProvider: ApiPlatformAdminDataProvider = {
910
getList: () => Promise.resolve({ data: [], total: 0 }),
10-
getOne: () => Promise.resolve({ data: { id: 'id' } }),
11+
getOne: <RecordType extends ApiPlatformAdminRecord>() =>
12+
Promise.resolve({ data: { id: 'id' } } as { data: RecordType }),
1113
getMany: () => Promise.resolve({ data: [] }),
1214
getManyReference: () => Promise.resolve({ data: [], total: 0 }),
13-
update: () => Promise.resolve({ data: {} }),
15+
update: <RecordType extends ApiPlatformAdminRecord>() =>
16+
Promise.resolve({ data: {} } as { data: RecordType }),
1417
updateMany: () => Promise.resolve({ data: [] }),
15-
create: () => Promise.resolve({ data: null }),
16-
delete: () => Promise.resolve({ data: { id: 'id' } }),
18+
create: <RecordType extends ApiPlatformAdminRecord>() =>
19+
Promise.resolve({ data: {} } as { data: RecordType }),
20+
delete: <RecordType extends ApiPlatformAdminRecord>() =>
21+
Promise.resolve({ data: { id: 'id' } } as { data: RecordType }),
1722
deleteMany: () => Promise.resolve({ data: [] }),
18-
} as DataProvider;
23+
introspect: () => Promise.resolve({ data: API_DATA }),
24+
subscribe: () => Promise.resolve({ data: null }),
25+
unsubscribe: () => Promise.resolve({ data: null }),
26+
};
1927

2028
describe('<AdminGuesser />', () => {
21-
const renderer = new ShallowRenderer();
29+
const renderer = createRenderer();
2230

2331
test('renders loading', () => {
24-
const tree = renderer.render(
32+
renderer.render(
2533
<AdminResourcesGuesser
2634
resources={[]}
2735
loading={true}
@@ -30,11 +38,11 @@ describe('<AdminGuesser />', () => {
3038
/>,
3139
);
3240

33-
expect(tree).toMatchSnapshot();
41+
expect(renderer.getRenderOutput()).toMatchSnapshot();
3442
});
3543

3644
test('renders without custom resources', () => {
37-
const tree = renderer.render(
45+
renderer.render(
3846
<AdminResourcesGuesser
3947
resources={resources}
4048
loading={false}
@@ -43,11 +51,11 @@ describe('<AdminGuesser />', () => {
4351
/>,
4452
);
4553

46-
expect(tree).toMatchSnapshot();
54+
expect(renderer.getRenderOutput()).toMatchSnapshot();
4755
});
4856

4957
test('renders with custom resources', () => {
50-
const tree = renderer.render(
58+
renderer.render(
5159
<AdminResourcesGuesser
5260
resources={resources}
5361
loading={false}
@@ -57,11 +65,11 @@ describe('<AdminGuesser />', () => {
5765
</AdminResourcesGuesser>,
5866
);
5967

60-
expect(tree).toMatchSnapshot();
68+
expect(renderer.getRenderOutput()).toMatchSnapshot();
6169
});
6270

6371
test('renders without data', () => {
64-
const tree = renderer.render(
72+
renderer.render(
6573
<AdminResourcesGuesser
6674
resources={[]}
6775
loading={false}
@@ -70,6 +78,6 @@ describe('<AdminGuesser />', () => {
7078
/>,
7179
);
7280

73-
expect(tree).toMatchSnapshot();
81+
expect(renderer.getRenderOutput()).toMatchSnapshot();
7482
});
7583
});

src/AdminGuesser.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { ComponentType, useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import {
44
Admin,
@@ -9,8 +9,10 @@ import {
99
Loading,
1010
TranslationProvider,
1111
defaultI18nProvider,
12+
ErrorProps,
1213
} from 'react-admin';
13-
import { createHashHistory } from 'history';
14+
import { Resource } from '@api-platform/api-doc-parser';
15+
import { createHashHistory, createMemoryHistory } from 'history';
1416
import { createTheme as createMuiTheme } from '@material-ui/core';
1517

1618
import ErrorBoundary from './ErrorBoundary';
@@ -19,27 +21,27 @@ import ResourceGuesser from './ResourceGuesser';
1921
import SchemaAnalyzerContext from './SchemaAnalyzerContext';
2022
import { Layout } from './layout';
2123
import introspectReducer from './introspectReducer';
22-
import { ApiPlatformAdminDataProvider } from './types';
24+
import { ApiPlatformAdminDataProvider, SchemaAnalyzer } from './types';
2325

2426
export interface AdminGuesserProps extends AdminProps {
2527
dataProvider: ApiPlatformAdminDataProvider;
26-
schemaAnalyzer: any;
28+
schemaAnalyzer: SchemaAnalyzer;
2729
includeDeprecated: boolean;
2830
}
2931

3032
interface AdminGuesserWithErrorProps extends AdminGuesserProps {
31-
error: string;
33+
error: ComponentType<ErrorProps>;
3234
}
3335

3436
interface AdminResourcesGuesserProps extends Omit<AdminProps, 'loading'> {
35-
admin?: any;
37+
admin?: ComponentType<AdminProps>;
3638
includeDeprecated: boolean;
3739
loading: boolean;
38-
loadingPage?: any;
39-
resources: any;
40+
loadingPage?: ComponentType;
41+
resources: Resource[];
4042
}
4143

42-
const displayOverrideCode = (resources) => {
44+
const displayOverrideCode = (resources: Resource[]) => {
4345
if (process.env.NODE_ENV === 'production') return;
4446

4547
let code =
@@ -121,14 +123,17 @@ const AdminGuesser = ({
121123
children,
122124
...rest
123125
}: AdminGuesserProps) => {
124-
const [resources, setResources] = useState<unknown>();
126+
const [resources, setResources] = useState<Resource[]>([]);
125127
const [loading, setLoading] = useState(true);
126128
const [, setError] = useState();
127129
const [addedCustomRoutes, setAddedCustomRoutes] = useState<CustomRoutes>([]);
128130
const [introspect, setIntrospect] = useState(true);
129131

130132
if (!history) {
131-
history = typeof window === 'undefined' ? {} : createHashHistory();
133+
history =
134+
typeof window === 'undefined'
135+
? createMemoryHistory()
136+
: createHashHistory();
132137
}
133138

134139
useEffect(() => {
@@ -145,7 +150,7 @@ const AdminGuesser = ({
145150
dataProvider
146151
.introspect()
147152
.then(({ data, customRoutes = [] }) => {
148-
setResources(data.resources);
153+
setResources(data.resources || []);
149154
setAddedCustomRoutes(customRoutes);
150155
setIntrospect(false);
151156
setLoading(false);

src/CreateGuesser.tsx

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ import {
44
Create,
55
CreateProps,
66
FileInput,
7+
HttpError,
8+
Record as RaRecord,
79
SimpleForm,
10+
SimpleFormProps,
11+
useCheckMinimumRequiredProps,
812
useMutation,
913
useNotify,
1014
useRedirect,
1115
} from 'react-admin';
16+
import { Field, Resource } from '@api-platform/api-doc-parser';
1217
import InputGuesser from './InputGuesser';
13-
import Introspecter from './Introspecter';
18+
import Introspecter, { BaseIntrospecterProps } from './Introspecter';
19+
import { IntrospectedGuesserProps } from './types';
1420

15-
const displayOverrideCode = (schema, fields) => {
21+
const displayOverrideCode = (schema: Resource, fields: Field[]) => {
1622
if (process.env.NODE_ENV === 'production') return;
1723

1824
let code =
@@ -32,25 +38,15 @@ const displayOverrideCode = (schema, fields) => {
3238
console.info(code);
3339
};
3440

35-
interface IntrospectedCreateGuesserProps extends CreateProps {
36-
children: any;
37-
fields: any;
38-
initialValues: any;
39-
margin: any;
40-
readableFields: any;
41-
redirect: string;
42-
sanitizeEmptyValues: any;
43-
schema: any;
44-
schemaAnalyzer: any;
45-
simpleFormComponent: any;
46-
submitOnEnter: any;
47-
successMessage: any;
48-
toolbar: any;
49-
validate: any;
50-
variant: any;
51-
warnWhenUnsavedChanges: any;
52-
writableFields: any;
53-
}
41+
type CreateSimpleFormProps = CreateProps & Omit<SimpleFormProps, 'children'>;
42+
43+
type IntrospectedCreateGuesserProps = CreateSimpleFormProps &
44+
IntrospectedGuesserProps;
45+
46+
export type CreateGuesserProps = Omit<
47+
CreateSimpleFormProps & BaseIntrospecterProps,
48+
'component' | 'resource'
49+
>;
5450

5551
export const IntrospectedCreateGuesser = ({
5652
fields,
@@ -80,19 +76,18 @@ export const IntrospectedCreateGuesser = ({
8076
const notify = useNotify();
8177
const redirect = useRedirect();
8278

83-
let inputChildren = children;
84-
if (!inputChildren) {
79+
let inputChildren = React.Children.toArray(children);
80+
if (inputChildren.length === 0) {
8581
inputChildren = writableFields.map((field) => (
8682
<InputGuesser key={field.name} source={field.name} />
8783
));
8884
displayOverrideCode(schema, writableFields);
8985
}
9086

91-
if (!Array.isArray(inputChildren)) {
92-
inputChildren = [inputChildren];
93-
}
94-
95-
const hasFileField = inputChildren.some((child) => child.type === FileInput);
87+
const hasFileField = inputChildren.some(
88+
(child) =>
89+
typeof child === 'object' && 'type' in child && child.type === FileInput,
90+
);
9691

9792
const save = useCallback(
9893
async (values) => {
@@ -109,18 +104,21 @@ export const IntrospectedCreateGuesser = ({
109104
);
110105
const success = onSuccess
111106
? onSuccess
112-
: ({ data: newRecord }) => {
107+
: ({ data: newRecord }: { data: RaRecord }) => {
113108
notify(successMessage || 'ra.notification.created', 'info', {
114109
smart_count: 1,
115110
});
116111
redirect(redirectTo, basePath, newRecord.id, newRecord);
117112
};
118113
success(response);
114+
return;
119115
} catch (error) {
120-
const submissionErrors = schemaAnalyzer.getSubmissionErrors(error);
116+
const submissionErrors = schemaAnalyzer.getSubmissionErrors(
117+
error as HttpError,
118+
);
121119
const failure = onFailure
122120
? onFailure
123-
: (error) => {
121+
: (error: string | Error) => {
124122
let message = 'ra.notification.http_error';
125123
if (!submissionErrors) {
126124
message =
@@ -135,7 +133,7 @@ export const IntrospectedCreateGuesser = ({
135133
: undefined,
136134
});
137135
};
138-
failure(error);
136+
failure(error as string | Error);
139137
if (submissionErrors) {
140138
return submissionErrors;
141139
}
@@ -176,9 +174,20 @@ export const IntrospectedCreateGuesser = ({
176174
);
177175
};
178176

179-
const CreateGuesser = (props) => (
180-
<Introspecter component={IntrospectedCreateGuesser} {...props} />
181-
);
177+
const CreateGuesser = (props: CreateGuesserProps) => {
178+
useCheckMinimumRequiredProps('CreateGuesser', ['resource'], props);
179+
if (!props.resource) {
180+
return null;
181+
}
182+
183+
return (
184+
<Introspecter
185+
component={IntrospectedCreateGuesser}
186+
resource={props.resource}
187+
{...props}
188+
/>
189+
);
190+
};
182191

183192
CreateGuesser.propTypes = {
184193
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),

0 commit comments

Comments
 (0)