diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8475734f..9ccb2d31 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## 3.4.0
+* Add enum input guesser
* Handle multiple file upload
* Allow to use tabbed components in guessers
* Use native react-admin `sanitizeEmptyValues`
diff --git a/jest.config.ts b/jest.config.ts
index bd18033e..9b51dc89 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -5,6 +5,7 @@ const config: Config.InitialOptions = {
setupFilesAfterEnv: ['./jest.setup.ts'],
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['/node_modules/', '/lib/'],
+ maxWorkers: 1,
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
diff --git a/package.json b/package.json
index e596c915..393218e6 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"license": "MIT",
"sideEffects": false,
"dependencies": {
- "@api-platform/api-doc-parser": "^0.15.4",
+ "@api-platform/api-doc-parser": "^0.16.1",
"history": "^5.0.0",
"jsonld": "^8.1.0",
"lodash.isplainobject": "^4.0.6",
@@ -68,8 +68,8 @@
"eslint-check": "eslint-config-prettier .eslintrc.cjs",
"fix": "eslint --ignore-pattern 'lib/*' --ext .ts,.tsx,.js,.md --fix .",
"lint": "eslint --ignore-pattern 'lib/*' --ext .ts,.tsx,.js,.md .",
- "test": "NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=1 src",
- "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=1 --watch src",
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest src",
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch src",
"watch": "tsc --watch"
}
}
diff --git a/src/InputGuesser.test.tsx b/src/InputGuesser.test.tsx
index 5433cfe1..aae00aae 100644
--- a/src/InputGuesser.test.tsx
+++ b/src/InputGuesser.test.tsx
@@ -52,6 +52,8 @@ const dataProvider: ApiPlatformAdminDataProvider = {
address: '16 avenue de Rivoli',
},
],
+ formatType: 'https://schema.org/EBook',
+ status: 'AVAILABLE',
},
}),
introspect: () =>
@@ -240,4 +242,100 @@ describe('', () => {
});
});
});
+
+ test.each([
+ // Default enum names.
+ {
+ transformEnum: undefined,
+ enums: {
+ formatType: [
+ 'Https://schema.org/ebook',
+ 'Https://schema.org/audiobookformat',
+ 'Https://schema.org/hardcover',
+ ],
+ status: ['Available', 'Sold out'],
+ },
+ },
+ // Custom transformation.
+ {
+ transformEnum: (value: string | number): string =>
+ `${value}`
+ .split('/')
+ .slice(-1)[0]
+ ?.replace(/([a-z])([A-Z])/, '$1_$2')
+ .toUpperCase() ?? '',
+ enums: {
+ formatType: ['EBOOK', 'AUDIOBOOK_FORMAT', 'HARDCOVER'],
+ status: ['AVAILABLE', 'SOLD_OUT'],
+ },
+ },
+ ])(
+ 'renders enum input with transformation',
+ async ({ transformEnum, enums }) => {
+ let updatedData = {};
+
+ render(
+
+
+
+
+ {
+ updatedData = data;
+ }}>
+
+
+
+
+
+
+ ,
+ );
+
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [fieldId, options] of Object.entries(enums)) {
+ // eslint-disable-next-line no-await-in-loop
+ const field = await screen.findByLabelText(
+ `resources.users.fields.${fieldId}`,
+ );
+ expect(field).toBeVisible();
+ if (field) {
+ fireEvent.mouseDown(field);
+ }
+ // First option is selected.
+ expect(
+ screen.queryAllByRole('option', { name: options[0], selected: true })
+ .length,
+ ).toEqual(1);
+ expect(
+ screen.queryAllByRole('option', { selected: false }).length,
+ ).toEqual(options.length);
+
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ options.forEach((option) => {
+ expect(
+ screen.queryAllByRole('option', { name: option }).length,
+ ).toEqual(1);
+ });
+ // Select last option.
+ const lastOption = screen.getByText(options.slice(-1)[0] ?? '');
+ fireEvent.click(lastOption);
+ }
+
+ const saveButton = screen.getByRole('button', { name: 'ra.action.save' });
+ fireEvent.click(saveButton);
+ await waitFor(() => {
+ expect(updatedData).toMatchObject({
+ formatType: 'https://schema.org/Hardcover',
+ status: 'SOLD_OUT',
+ });
+ });
+ },
+ );
});
diff --git a/src/InputGuesser.tsx b/src/InputGuesser.tsx
index 1984070f..bd16c847 100644
--- a/src/InputGuesser.tsx
+++ b/src/InputGuesser.tsx
@@ -41,6 +41,7 @@ export const IntrospectedInputGuesser = ({
schema,
schemaAnalyzer,
validate,
+ transformEnum,
...props
}: IntrospectedInputGuesserProps) => {
const field = fields.find(({ name }) => name === props.source);
@@ -105,6 +106,26 @@ export const IntrospectedInputGuesser = ({
let parse;
const fieldType = schemaAnalyzer.getFieldType(field);
+ if (field.enum) {
+ const choices = Object.entries(field.enum).map(([k, v]) => ({
+ id: v,
+ name: transformEnum ? transformEnum(v) : k,
+ }));
+ return fieldType === 'array' ? (
+
+ ) : (
+
+ );
+ }
+
if (['integer_id', 'id'].includes(fieldType) || field.name === 'id') {
const prefix = `/${props.resource}/`;
diff --git a/src/__fixtures__/parsedData.ts b/src/__fixtures__/parsedData.ts
index 9671bd9d..791829e4 100644
--- a/src/__fixtures__/parsedData.ts
+++ b/src/__fixtures__/parsedData.ts
@@ -81,4 +81,25 @@ export const API_FIELDS_DATA = [
embedded: EmbeddedResource,
required: false,
}),
+ new Field('formatType', {
+ id: 'https://schema.org/BookFormatType',
+ range: 'http://www.w3.org/2001/XMLSchema#string',
+ reference: null,
+ embedded: null,
+ enum: {
+ 'Https://schema.org/ebook': 'https://schema.org/EBook',
+ 'Https://schema.org/audiobookformat':
+ 'https://schema.org/AudiobookFormat',
+ 'Https://schema.org/hardcover': 'https://schema.org/Hardcover',
+ },
+ required: false,
+ }),
+ new Field('status', {
+ id: 'http://localhost/status',
+ range: 'http://www.w3.org/2001/XMLSchema#string',
+ reference: null,
+ embedded: null,
+ enum: { Available: 'AVAILABLE', 'Sold out': 'SOLD_OUT' },
+ required: false,
+ }),
];
diff --git a/src/types.ts b/src/types.ts
index 46d363bc..b10f88a9 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -474,12 +474,16 @@ type InputProps =
| ReferenceInputProps;
export type IntrospectedInputGuesserProps = Partial &
- IntrospectedGuesserProps;
+ IntrospectedGuesserProps & {
+ transformEnum?: (value: string | number) => string | number;
+ };
export type InputGuesserProps = Omit<
InputProps & Omit,
'component'
->;
+> & {
+ transformEnum?: (value: string | number) => string | number;
+};
export type IntrospecterProps = (
| CreateGuesserProps