mirror of
https://github.com/axios/axios.git
synced 2026-04-11 14:21:59 +08:00
feat: support react native blob objects (#5764)
* feat: support react native blob objects * test: cover react native blob objects detection * fix: improve isReactNativeBlob and isReactNative checks for better validation * feat: support appending React Native blob objects to FormData without recursion --------- Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
parent
00d97b9730
commit
885b4af6f5
@ -156,6 +156,11 @@ function toFormData(obj, formData, options) {
|
|||||||
function defaultVisitor(value, key, path) {
|
function defaultVisitor(value, key, path) {
|
||||||
let arr = value;
|
let arr = value;
|
||||||
|
|
||||||
|
if (utils.isReactNative(formData) && utils.isReactNativeBlob(value)) {
|
||||||
|
formData.append(renderKey(path, key, dots), convertValue(value));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (value && !path && typeof value === 'object') {
|
if (value && !path && typeof value === 'object') {
|
||||||
if (utils.endsWith(key, '{}')) {
|
if (utils.endsWith(key, '{}')) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|||||||
27
lib/utils.js
27
lib/utils.js
@ -186,6 +186,31 @@ const isDate = kindOfTest('Date');
|
|||||||
*/
|
*/
|
||||||
const isFile = kindOfTest('File');
|
const isFile = kindOfTest('File');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a value is a React Native Blob
|
||||||
|
* React Native "blob": an object with a `uri` attribute. Optionally, it can
|
||||||
|
* also have a `name` and `type` attribute to specify filename and content type
|
||||||
|
*
|
||||||
|
* @see https://github.com/facebook/react-native/blob/26684cf3adf4094eb6c405d345a75bf8c7c0bf88/Libraries/Network/FormData.js#L68-L71
|
||||||
|
*
|
||||||
|
* @param {*} value The value to test
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if value is a React Native Blob, otherwise false
|
||||||
|
*/
|
||||||
|
const isReactNativeBlob = (value) => {
|
||||||
|
return !!(value && typeof value.uri !== 'undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if environment is React Native
|
||||||
|
* ReactNative `FormData` has a non-standard `getParts()` method
|
||||||
|
*
|
||||||
|
* @param {*} formData The formData to test
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if environment is React Native, otherwise false
|
||||||
|
*/
|
||||||
|
const isReactNative = (formData) => formData && typeof formData.getParts !== 'undefined';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Blob
|
* Determine if a value is a Blob
|
||||||
*
|
*
|
||||||
@ -850,6 +875,8 @@ export default {
|
|||||||
isUndefined,
|
isUndefined,
|
||||||
isDate,
|
isDate,
|
||||||
isFile,
|
isFile,
|
||||||
|
isReactNativeBlob,
|
||||||
|
isReactNative,
|
||||||
isBlob,
|
isBlob,
|
||||||
isRegExp,
|
isRegExp,
|
||||||
isFunction,
|
isFunction,
|
||||||
|
|||||||
@ -3,6 +3,19 @@ import toFormData from '../../../lib/helpers/toFormData.js';
|
|||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
|
|
||||||
describe('helpers::toFormData', function () {
|
describe('helpers::toFormData', function () {
|
||||||
|
function createRNFormDataSpy() {
|
||||||
|
const calls = [];
|
||||||
|
return {
|
||||||
|
calls,
|
||||||
|
append(key, value) {
|
||||||
|
calls.push([key, value]);
|
||||||
|
},
|
||||||
|
getParts() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
it('should convert a flat object to FormData', function () {
|
it('should convert a flat object to FormData', function () {
|
||||||
const data = {
|
const data = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
@ -50,4 +63,70 @@ describe('helpers::toFormData', function () {
|
|||||||
const formData = toFormData(data, new FormData());
|
const formData = toFormData(data, new FormData());
|
||||||
assert.ok(formData instanceof FormData);
|
assert.ok(formData instanceof FormData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should append root-level React Native blob without recursion', function () {
|
||||||
|
const formData = createRNFormDataSpy();
|
||||||
|
|
||||||
|
const blob = {
|
||||||
|
uri: 'file://test.png',
|
||||||
|
type: 'image/png',
|
||||||
|
name: 'test.png'
|
||||||
|
};
|
||||||
|
|
||||||
|
toFormData({ file: blob }, formData);
|
||||||
|
|
||||||
|
assert.strictEqual(formData.calls.length, 1);
|
||||||
|
assert.strictEqual(formData.calls[0][0], 'file');
|
||||||
|
assert.strictEqual(formData.calls[0][1], blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should append nested React Native blob without recursion', function () {
|
||||||
|
const formData = createRNFormDataSpy();
|
||||||
|
|
||||||
|
const blob = {
|
||||||
|
uri: 'file://nested.png',
|
||||||
|
type: 'image/png',
|
||||||
|
name: 'nested.png'
|
||||||
|
};
|
||||||
|
|
||||||
|
toFormData({ nested: { file: blob } }, formData);
|
||||||
|
|
||||||
|
assert.strictEqual(formData.calls.length, 1);
|
||||||
|
assert.strictEqual(formData.calls[0][0], 'nested[file]');
|
||||||
|
assert.strictEqual(formData.calls[0][1], blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should append deeply nested React Native blob without recursion', function () {
|
||||||
|
const formData = createRNFormDataSpy();
|
||||||
|
|
||||||
|
const blob = {
|
||||||
|
uri: 'file://deep.png',
|
||||||
|
name: 'deep.png'
|
||||||
|
};
|
||||||
|
|
||||||
|
toFormData({ a: { b: { c: blob } } }, formData);
|
||||||
|
|
||||||
|
assert.strictEqual(formData.calls.length, 1);
|
||||||
|
assert.strictEqual(formData.calls[0][0], 'a[b][c]');
|
||||||
|
assert.strictEqual(formData.calls[0][1], blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT recurse into React Native blob properties', function () {
|
||||||
|
const formData = createRNFormDataSpy();
|
||||||
|
|
||||||
|
const blob = {
|
||||||
|
uri: 'file://nope.png',
|
||||||
|
type: 'image/png',
|
||||||
|
name: 'nope.png'
|
||||||
|
};
|
||||||
|
|
||||||
|
toFormData({ file: blob }, formData);
|
||||||
|
|
||||||
|
const keys = formData.calls.map(call => call[0]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(keys, ['file']);
|
||||||
|
assert.ok(!keys.some(k => k.includes('uri')));
|
||||||
|
assert.ok(!keys.some(k => k.includes('type')));
|
||||||
|
assert.ok(!keys.some(k => k.includes('name')));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -120,4 +120,57 @@ describe('utils', function () {
|
|||||||
assert.strictEqual(result, null);
|
assert.strictEqual(result, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('utils::isReactNativeBlob', function () {
|
||||||
|
it('should return true for objects with uri property', function () {
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({ uri: 'file://path/to/file' }), true);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({ uri: 'content://media/image' }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for React Native blob-like objects with optional name and type', function () {
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({
|
||||||
|
uri: 'file://path/to/file',
|
||||||
|
name: 'image.png',
|
||||||
|
type: 'image/png'
|
||||||
|
}), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for objects without uri property', function () {
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({ path: 'file://path' }), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({ url: 'http://example.com' }), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({}), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-objects', function () {
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob(null), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob(undefined), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob('string'), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob(123), false);
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob(false), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true even if uri is empty string', function () {
|
||||||
|
assert.strictEqual(utils.isReactNativeBlob({ uri: '' }), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('utils::isReactNative', function () {
|
||||||
|
it('should return true for FormData with getParts method', function () {
|
||||||
|
const mockReactNativeFormData = {
|
||||||
|
append: function() {},
|
||||||
|
getParts: function() { return []; }
|
||||||
|
};
|
||||||
|
assert.strictEqual(utils.isReactNative(mockReactNativeFormData), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for standard FormData without getParts method', function () {
|
||||||
|
const standardFormData = new FormData();
|
||||||
|
assert.strictEqual(utils.isReactNative(standardFormData), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for objects without getParts method', function () {
|
||||||
|
assert.strictEqual(utils.isReactNative({ append: function() {} }), false);
|
||||||
|
assert.strictEqual(utils.isReactNative({}), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user