Repository: api-platform/admin
Pull request #134 (2018-11-16 13:21:51)

Adds tests and native support for unserialized unrelated arrays
| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #122
| License       | MIT
| Doc PR        | api-platform/doc#664

This PR supports a simple strategy to edit serialized array inputs from DB entities.
While it may not currently address more complicated array structures, this solution
supports simple one-dimensional, keyless(numerically indexed), arrays


Patch:
diff --git a/src/hydra/hydraClient.js b/src/hydra/hydraClient.js
index 6ce3463..0593ca7 100644
--- a/src/hydra/hydraClient.js
+++ b/src/hydra/hydraClient.js
@@ -168,9 +168,8 @@ export default ({entrypoint, resources = []}, httpClient = fetchHydra) => {
    * @returns {Object}
    */
   const convertReactAdminRequestToHydraRequest = (type, resource, params) => {
-    const entrypointUrl = new URL(entrypoint, window.location.href);
-    const collectionUrl = new URL(`${entrypoint}/${resource}`, entrypointUrl);
-    const itemUrl = new URL(params.id, entrypointUrl);
+    const collectionUrl = new URL(`${entrypoint}/${resource}`);
+    const itemUrl = new URL(params.id, entrypoint);
 
     switch (type) {
       case CREATE:
diff --git a/src/inputFactory.js b/src/inputFactory.js
index db020a4..2eddefb 100644
--- a/src/inputFactory.js
+++ b/src/inputFactory.js
@@ -1,4 +1,5 @@
 import {
+  ArrayInput,
   BooleanInput,
   DateInput,
   NumberInput,
@@ -7,6 +8,7 @@ import {
   required,
   SelectArrayInput,
   SelectInput,
+  SimpleFormIterator,
   TextInput,
 } from 'react-admin';
 import React from 'react';
@@ -72,6 +74,15 @@ export default (field, options) => {
   }
 
   switch (field.range) {
+    case 'http://www.w3.org/2001/XMLSchema#array':
+      return (
+        <ArrayInput key={field.name} source={field.name} {...props}>
+          <SimpleFormIterator>
+            <TextInput />
+          </SimpleFormIterator>
+        </ArrayInput>
+      );
+
     case 'http://www.w3.org/2001/XMLSchema#integer':
       return <NumberInput key={field.name} source={field.name} {...props} />;
 


Test patch:
diff --git a/src/Create.test.js b/src/Create.test.js
index def7b8c..e744a59 100644
--- a/src/Create.test.js
+++ b/src/Create.test.js
@@ -1,7 +1,7 @@
 import Api from '@api-platform/api-doc-parser/lib/Api';
 import Field from '@api-platform/api-doc-parser/lib/Field';
 import Resource from '@api-platform/api-doc-parser/lib/Resource';
-import {TextInput} from 'react-admin';
+import {ArrayInput, SimpleFormIterator, TextInput} from 'react-admin';
 import {shallow} from 'enzyme';
 import React from 'react';
 import Create from './Create';
@@ -36,6 +36,12 @@ const resourceData = {
       required: true,
       deprecated: true,
     }),
+    new Field('fieldD', {
+      id: 'http://schema.org/fieldD',
+      range: 'http://www.w3.org/2001/XMLSchema#array',
+      reference: null,
+      required: true,
+    }),
   ],
 };
 
@@ -67,8 +73,11 @@ describe('<Create />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(2);
-    expect(render.find(TextInput).length).toEqual(2);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(3);
+    expect(render.find(ArrayInput).length).toEqual(1);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
   });
@@ -104,9 +113,12 @@ describe('<Create />', () => {
       />,
     );
 
-    expect(customInputFactory).toHaveBeenCalledTimes(2);
+    expect(customInputFactory).toHaveBeenCalledTimes(3);
     expect(defaultInputFactory).toHaveBeenCalledTimes(0);
-    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(ArrayInput).length).toEqual(1);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
   });
@@ -123,6 +135,12 @@ describe('<Create />', () => {
           reference: null,
           required: true,
         }),
+        new Field('fieldE', {
+          id: 'http://schema.org/fieldE',
+          range: 'http://www.w3.org/2001/XMLSchema#array',
+          reference: null,
+          required: true,
+        }),
       ],
     });
 
@@ -144,8 +162,11 @@ describe('<Create />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(1);
-    expect(render.find(TextInput).length).toEqual(1);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(2);
+    expect(render.find(ArrayInput).length).toEqual(1);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldE');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldC');
   });
 });
diff --git a/src/Edit.test.js b/src/Edit.test.js
index f46218c..77d7980 100644
--- a/src/Edit.test.js
+++ b/src/Edit.test.js
@@ -1,7 +1,12 @@
 import Api from '@api-platform/api-doc-parser/lib/Api';
 import Field from '@api-platform/api-doc-parser/lib/Field';
 import Resource from '@api-platform/api-doc-parser/lib/Resource';
-import {DisabledInput, TextInput} from 'react-admin';
+import {
+  ArrayInput,
+  DisabledInput,
+  SimpleFormIterator,
+  TextInput,
+} from 'react-admin';
 import {shallow} from 'enzyme';
 import React from 'react';
 import Edit from './Edit';
@@ -36,6 +41,12 @@ const resourceData = {
       required: true,
       deprecated: true,
     }),
+    new Field('fieldD', {
+      id: 'http://schema.org/fieldD',
+      range: 'http://www.w3.org/2001/XMLSchema#array',
+      reference: null,
+      required: true,
+    }),
   ],
 };
 
@@ -67,10 +78,13 @@ describe('<Edit />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(2);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(3);
+    expect(render.find(ArrayInput).length).toEqual(1);
     expect(render.find(DisabledInput).length).toEqual(1);
     expect(render.find(DisabledInput).get(0).props.source).toEqual('id');
-    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
   });
@@ -103,9 +117,11 @@ describe('<Edit />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(2);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(3);
+    expect(render.find(ArrayInput).length).toEqual(1);
     expect(render.find(DisabledInput).length).toEqual(0);
-    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
   });
@@ -141,11 +157,14 @@ describe('<Edit />', () => {
       />,
     );
 
-    expect(customInputFactory).toHaveBeenCalledTimes(2);
+    expect(customInputFactory).toHaveBeenCalledTimes(3);
     expect(defaultInputFactory).toHaveBeenCalledTimes(0);
+    expect(render.find(ArrayInput).length).toEqual(1);
     expect(render.find(DisabledInput).length).toEqual(1);
     expect(render.find(DisabledInput).get(0).props.source).toEqual('id');
-    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
   });
@@ -162,6 +181,12 @@ describe('<Edit />', () => {
           reference: null,
           required: true,
         }),
+        new Field('fieldE', {
+          id: 'http://schema.org/fieldE',
+          range: 'http://www.w3.org/2001/XMLSchema#array',
+          reference: null,
+          required: true,
+        }),
       ],
     });
 
@@ -183,10 +208,13 @@ describe('<Edit />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(1);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(2);
+    expect(render.find(ArrayInput).length).toEqual(1);
     expect(render.find(DisabledInput).length).toEqual(1);
     expect(render.find(DisabledInput).get(0).props.source).toEqual('id');
-    expect(render.find(TextInput).length).toEqual(1);
+    expect(render.find(SimpleFormIterator).length).toEqual(1);
+    expect(render.find(TextInput).length).toEqual(2);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldE');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldC');
   });
 
@@ -227,11 +255,14 @@ describe('<Edit />', () => {
       />,
     );
 
-    expect(defaultInputFactory).toHaveBeenCalledTimes(3);
+    expect(defaultInputFactory).toHaveBeenCalledTimes(4);
+    expect(render.find(ArrayInput).length).toEqual(1);
     expect(render.find(DisabledInput).length).toEqual(0);
-    expect(render.find(TextInput).length).toEqual(3);
+    expect(render.find(TextInput).length).toEqual(4);
+    expect(render.find(ArrayInput).get(0).props.source).toEqual('fieldD');
     expect(render.find(TextInput).get(0).props.source).toEqual('fieldA');
     expect(render.find(TextInput).get(1).props.source).toEqual('fieldB');
-    expect(render.find(TextInput).get(2).props.source).toEqual('id');
+    expect(render.find(TextInput).get(2).props.source).toEqual(undefined);
+    expect(render.find(TextInput).get(3).props.source).toEqual('id');
   });
 });
diff --git a/src/hydra/hydraClient.test.js b/src/hydra/hydraClient.test.js
index a05c8e4..a436c00 100644
--- a/src/hydra/hydraClient.test.js
+++ b/src/hydra/hydraClient.test.js
@@ -100,26 +100,6 @@ describe('fetch data from an hydra api', () => {
     });
   });
 
-  test('fetch a get_list resource with relative api url', async () => {
-    const mockHttpClient = jest.fn();
-    mockHttpClient.mockReturnValue(
-      Promise.resolve({
-        json: {
-          'hydra:member': [{'@id': 'books/5'}],
-        },
-      }),
-    );
-
-    await hydraClient({entrypoint: '/api'}, mockHttpClient)(GET_LIST, 'books', {
-      pagination: {page: 2},
-      sort: {field: 'id', order: 'ASC'},
-    }).then(() => {
-      expect(mockHttpClient.mock.calls[0][0].href).toBe(
-        `${window.location.origin}/api/books?order%5Bid%5D=ASC&page=2`,
-      );
-    });
-  });
-
   test('fetch a get_many resource', async () => {
     const mockHttpClient = jest.fn();
     mockHttpClient