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) {
|
||||
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 (utils.endsWith(key, '{}')) {
|
||||
// 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');
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
@ -850,6 +875,8 @@ export default {
|
||||
isUndefined,
|
||||
isDate,
|
||||
isFile,
|
||||
isReactNativeBlob,
|
||||
isReactNative,
|
||||
isBlob,
|
||||
isRegExp,
|
||||
isFunction,
|
||||
|
||||
@ -3,6 +3,19 @@ import toFormData from '../../../lib/helpers/toFormData.js';
|
||||
import FormData from 'form-data';
|
||||
|
||||
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 () {
|
||||
const data = {
|
||||
foo: 'bar',
|
||||
@ -50,4 +63,70 @@ describe('helpers::toFormData', function () {
|
||||
const formData = toFormData(data, new 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);
|
||||
});
|
||||
});
|
||||
|
||||
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