diff --git a/admin/javascript/src/silverstripe-backend.js b/admin/javascript/src/silverstripe-backend.js index d11093b37..87ba8cd58 100644 --- a/admin/javascript/src/silverstripe-backend.js +++ b/admin/javascript/src/silverstripe-backend.js @@ -1,6 +1,7 @@ import fetch from 'isomorphic-fetch'; import es6promise from 'es6-promise'; import qs from 'qs'; +import merge from 'merge'; es6promise.polyfill(); @@ -50,6 +51,8 @@ class SilverStripeBackend { * - responseFormat: the content-type of the response data. Decoding will be handled for you. * - payloadSchema: Definition for how the payload data passed into the created method * will be processed. See "Payload Schema" + * - defaultData: Data to merge into the payload + * (which is passed into the returned method when invoked) * * # Payload Formats * @@ -257,6 +260,7 @@ class SilverStripeBackend { payloadFormat: 'application/x-www-form-url-encoded', responseFormat: 'application/json', payloadSchema: {}, + defaultData: {}, }, endpointSpec); // Substitute shorcut format values with their full mime types @@ -270,18 +274,20 @@ class SilverStripeBackend { } ); - return (data) => { + return (data = {}) => { const headers = { Accept: refinedSpec.responseFormat, 'Content-Type': refinedSpec.payloadFormat, }; + const mergedData = merge.recursive({}, refinedSpec.defaultData, data); + // Replace url placeholders, and add query parameters // from the payload based on the schema spec. const url = applySchemaToUrl( refinedSpec.payloadSchema, refinedSpec.url, - data, + mergedData, // Always add full payload data to GET requests. // GET requests with a HTTP body are technically legal, // but throw an error in the WHATWG fetch() implementation. @@ -292,7 +298,7 @@ class SilverStripeBackend { refinedSpec.payloadFormat, // Filter raw data through the defined schema, // potentially removing keys because they're - applySchemaToData(refinedSpec.payloadSchema, data) + applySchemaToData(refinedSpec.payloadSchema, mergedData) ); const args = refinedSpec.method.toLowerCase() === 'get' diff --git a/admin/javascript/src/tests/silverstripe-backend-test.js b/admin/javascript/src/tests/silverstripe-backend-test.js index a5322f919..7073a073c 100644 --- a/admin/javascript/src/tests/silverstripe-backend-test.js +++ b/admin/javascript/src/tests/silverstripe-backend-test.js @@ -4,6 +4,7 @@ jest.unmock('isomorphic-fetch'); jest.unmock('../silverstripe-backend'); jest.unmock('qs'); +jest.unmock('merge'); import backend from '../silverstripe-backend'; @@ -191,10 +192,10 @@ describe('SilverStripeBackend', () => { two: { urlReplacement: ':two' }, }, }); - const promise = endpoint({ + endpoint({ one: 1, two: 2, - three: 3 + three: 3, }); expect(mock.post.mock.calls[0][0]).toEqual('http://example.com/1/2/?foo=bar'); expect(mock.post.mock.calls[0][1]).toEqual('two=2&three=3'); @@ -217,10 +218,10 @@ describe('SilverStripeBackend', () => { three: { querystring: true }, }, }); - const promise = endpoint({ + endpoint({ one: 1, two: 2, - three: 3 + three: 3, }); expect(mock.post.mock.calls[0][0]).toEqual('http://example.com/1/2/?foo=bar&three=3'); expect(mock.post.mock.calls[0][1]).toEqual('{"two":2}'); @@ -242,16 +243,43 @@ describe('SilverStripeBackend', () => { three: { querystring: true }, }, }); - const promise = endpoint({ + endpoint({ one: 1, two: 2, - three: 3 + three: 3, }); expect(mock.get.mock.calls[0][0]).toEqual('http://example.com/1/2/?foo=bar&two=2&three=3'); expect(mock.get.mock.calls[0][1]).toEqual({ Accept: 'application/json', - 'Content-Type': 'application/x-www-form-url-encoded' + 'Content-Type': 'application/x-www-form-url-encoded', }); }); + + it('should merge defaultData into data argument', () => { + const mock = getBackendMock({ + text: () => Promise.resolve('{"status":"ok"}'), + headers: new Headers({ + 'Content-Type': 'application/json', + }), + }); + const endpoint = mock.createEndpointFetcher({ + url: 'http://example.com/', + method: 'post', + payloadFormat: 'json', + defaultData: { one: 1, two: 2, four: { fourOne: true } }, + }); + endpoint({ + two: 'updated', + three: 3, + four: { fourTwo: true }, + }); + expect(mock.post.mock.calls[0][0]).toEqual('http://example.com/'); + expect(mock.post.mock.calls[0][1]).toEqual(JSON.stringify({ + one: 1, + two: 'updated', + four: { fourOne: true, fourTwo: true }, + three: 3, + })); + }); }); }); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 01a5c6b02..8cc00ceff 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -12759,6 +12759,10 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/json-js/-/json-js-1.1.2.tgz" }, + "merge": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz" + }, "merge-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.0.tgz", diff --git a/package.json b/package.json index 93beed996..60ff105bd 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "isomorphic-fetch": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "jquery-sizes": "^0.33.0", "json-js": "^1.1.2", + "merge": "^1.2.0", "page.js": "^4.13.3", "qs": "^6.1.0", "react": "^0.14.8",