mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
b2415c4866
Summary: As per the XMLHttpRequest specification [1], getAllResponseHeaders() should return a string of headers with lowercased names and sorted by their uppercase representation, with each header ending with '\r\n'. [1] https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method ## Changelog [General] [Changed] XMLHttpRequest.getAllResponseHeaders() now returns headers with names lowercased and sorted in ascending order, as per specification Pull Request resolved: https://github.com/facebook/react-native/pull/32363 Test Plan: Test derived from Web Platform Test repository: https://github.com/web-platform-tests/wpt/tree/master/xhr Reviewed By: yungsters Differential Revision: D31626217 Pulled By: sota000 fbshipit-source-id: 299d005facbe1c15b8cda5eed6750db75addca80
314 lines
9.2 KiB
JavaScript
314 lines
9.2 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @emails oncall+react_native
|
|
*/
|
|
|
|
import createPerformanceLogger from '../../Utilities/createPerformanceLogger';
|
|
|
|
jest.unmock('../../Utilities/Platform');
|
|
jest.mock('../../Utilities/GlobalPerformanceLogger');
|
|
|
|
const Platform = require('../../Utilities/Platform');
|
|
const GlobalPerformanceLogger = require('../../Utilities/GlobalPerformanceLogger');
|
|
let requestId = 1;
|
|
|
|
function setRequestId(id) {
|
|
if (Platform.OS === 'ios') {
|
|
return;
|
|
}
|
|
requestId = id;
|
|
}
|
|
|
|
jest
|
|
.dontMock('event-target-shim')
|
|
.setMock('../../BatchedBridge/NativeModules', {
|
|
Networking: {
|
|
addListener: function() {},
|
|
removeListeners: function() {},
|
|
sendRequest(options, callback) {
|
|
if (typeof callback === 'function') {
|
|
// android does not pass a callback
|
|
callback(requestId);
|
|
}
|
|
},
|
|
abortRequest: function() {},
|
|
},
|
|
PlatformConstants: {
|
|
getConstants() {
|
|
return {};
|
|
},
|
|
},
|
|
});
|
|
|
|
const XMLHttpRequest = require('../XMLHttpRequest');
|
|
|
|
describe('XMLHttpRequest', function() {
|
|
let xhr;
|
|
let handleTimeout;
|
|
let handleError;
|
|
let handleLoad;
|
|
let handleReadyStateChange;
|
|
let handleLoadEnd;
|
|
|
|
beforeEach(() => {
|
|
xhr = new XMLHttpRequest();
|
|
|
|
xhr.ontimeout = jest.fn();
|
|
xhr.onerror = jest.fn();
|
|
xhr.onload = jest.fn();
|
|
xhr.onloadend = jest.fn();
|
|
xhr.onreadystatechange = jest.fn();
|
|
|
|
handleTimeout = jest.fn();
|
|
handleError = jest.fn();
|
|
handleLoad = jest.fn();
|
|
handleLoadEnd = jest.fn();
|
|
handleReadyStateChange = jest.fn();
|
|
|
|
xhr.addEventListener('timeout', handleTimeout);
|
|
xhr.addEventListener('error', handleError);
|
|
xhr.addEventListener('load', handleLoad);
|
|
xhr.addEventListener('loadend', handleLoadEnd);
|
|
xhr.addEventListener('readystatechange', handleReadyStateChange);
|
|
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
xhr = null;
|
|
handleTimeout = null;
|
|
handleError = null;
|
|
handleLoad = null;
|
|
handleLoadEnd = null;
|
|
handleReadyStateChange = null;
|
|
});
|
|
|
|
it('should transition readyState correctly', function() {
|
|
expect(xhr.readyState).toBe(xhr.UNSENT);
|
|
|
|
xhr.open('GET', 'blabla');
|
|
|
|
expect(xhr.onreadystatechange.mock.calls.length).toBe(1);
|
|
expect(handleReadyStateChange.mock.calls.length).toBe(1);
|
|
expect(xhr.readyState).toBe(xhr.OPENED);
|
|
});
|
|
|
|
it('should expose responseType correctly', function() {
|
|
expect(xhr.responseType).toBe('');
|
|
|
|
jest.spyOn(console, 'warn').mockReturnValue(undefined);
|
|
|
|
// Setting responseType to an unsupported value has no effect.
|
|
xhr.responseType = 'arrayblobbuffertextfile';
|
|
expect(xhr.responseType).toBe('');
|
|
|
|
expect(console.warn).toBeCalledWith(
|
|
"The provided value 'arrayblobbuffertextfile' is not a valid 'responseType'.",
|
|
);
|
|
console.warn.mockRestore();
|
|
|
|
xhr.responseType = 'arraybuffer';
|
|
expect(xhr.responseType).toBe('arraybuffer');
|
|
|
|
// Can't change responseType after first data has been received.
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
expect(() => {
|
|
xhr.responseType = 'text';
|
|
}).toThrow();
|
|
});
|
|
|
|
it('should expose responseText correctly', function() {
|
|
xhr.responseType = '';
|
|
expect(xhr.responseText).toBe('');
|
|
expect(xhr.response).toBe('');
|
|
|
|
xhr.responseType = 'arraybuffer';
|
|
expect(() => xhr.responseText).toThrow();
|
|
expect(xhr.response).toBe(null);
|
|
|
|
xhr.responseType = 'text';
|
|
expect(xhr.responseText).toBe('');
|
|
expect(xhr.response).toBe('');
|
|
|
|
// responseText is read-only.
|
|
expect(() => {
|
|
xhr.responseText = 'hi';
|
|
}).toThrow();
|
|
expect(xhr.responseText).toBe('');
|
|
expect(xhr.response).toBe('');
|
|
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(2);
|
|
xhr.__didReceiveData(requestId, 'Some data');
|
|
expect(xhr.responseText).toBe('Some data');
|
|
});
|
|
|
|
it('should call ontimeout function when the request times out', function() {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(3);
|
|
xhr.__didCompleteResponse(requestId, 'Timeout', true);
|
|
xhr.__didCompleteResponse(requestId, 'Timeout', true);
|
|
|
|
expect(xhr.readyState).toBe(xhr.DONE);
|
|
|
|
expect(xhr.ontimeout.mock.calls.length).toBe(1);
|
|
expect(xhr.onloadend.mock.calls.length).toBe(1);
|
|
expect(xhr.onerror).not.toBeCalled();
|
|
expect(xhr.onload).not.toBeCalled();
|
|
|
|
expect(handleTimeout.mock.calls.length).toBe(1);
|
|
expect(handleLoadEnd.mock.calls.length).toBe(1);
|
|
expect(handleError).not.toBeCalled();
|
|
expect(handleLoad).not.toBeCalled();
|
|
});
|
|
|
|
it('should call onerror function when the request times out', function() {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(4);
|
|
xhr.__didCompleteResponse(requestId, 'Generic error');
|
|
|
|
expect(xhr.readyState).toBe(xhr.DONE);
|
|
|
|
expect(xhr.onreadystatechange.mock.calls.length).toBe(2);
|
|
expect(xhr.onerror.mock.calls.length).toBe(1);
|
|
expect(xhr.onloadend.mock.calls.length).toBe(1);
|
|
expect(xhr.ontimeout).not.toBeCalled();
|
|
expect(xhr.onload).not.toBeCalled();
|
|
|
|
expect(handleReadyStateChange.mock.calls.length).toBe(2);
|
|
expect(handleError.mock.calls.length).toBe(1);
|
|
expect(handleLoadEnd.mock.calls.length).toBe(1);
|
|
expect(handleTimeout).not.toBeCalled();
|
|
expect(handleLoad).not.toBeCalled();
|
|
});
|
|
|
|
it('should call onload function when there is no error', function() {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(5);
|
|
xhr.__didCompleteResponse(requestId, null);
|
|
|
|
expect(xhr.readyState).toBe(xhr.DONE);
|
|
|
|
expect(xhr.onreadystatechange.mock.calls.length).toBe(2);
|
|
expect(xhr.onload.mock.calls.length).toBe(1);
|
|
expect(xhr.onloadend.mock.calls.length).toBe(1);
|
|
expect(xhr.onerror).not.toBeCalled();
|
|
expect(xhr.ontimeout).not.toBeCalled();
|
|
|
|
expect(handleReadyStateChange.mock.calls.length).toBe(2);
|
|
expect(handleLoad.mock.calls.length).toBe(1);
|
|
expect(handleLoadEnd.mock.calls.length).toBe(1);
|
|
expect(handleError).not.toBeCalled();
|
|
expect(handleTimeout).not.toBeCalled();
|
|
});
|
|
|
|
it('should call upload onprogress', function() {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
|
|
xhr.upload.onprogress = jest.fn();
|
|
const handleProgress = jest.fn();
|
|
xhr.upload.addEventListener('progress', handleProgress);
|
|
setRequestId(6);
|
|
xhr.__didUploadProgress(requestId, 42, 100);
|
|
|
|
expect(xhr.upload.onprogress.mock.calls.length).toBe(1);
|
|
expect(handleProgress.mock.calls.length).toBe(1);
|
|
|
|
expect(xhr.upload.onprogress.mock.calls[0][0].loaded).toBe(42);
|
|
expect(xhr.upload.onprogress.mock.calls[0][0].total).toBe(100);
|
|
expect(handleProgress.mock.calls[0][0].loaded).toBe(42);
|
|
expect(handleProgress.mock.calls[0][0].total).toBe(100);
|
|
});
|
|
|
|
it('should combine response headers with CRLF', function() {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(7);
|
|
xhr.__didReceiveResponse(requestId, 200, {
|
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
'Content-Length': '32',
|
|
});
|
|
|
|
expect(xhr.getAllResponseHeaders()).toBe(
|
|
'content-length: 32\r\n' + 'content-type: text/plain; charset=utf-8\r\n',
|
|
);
|
|
});
|
|
|
|
it('should log to GlobalPerformanceLogger if a custom performance logger is not set', () => {
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
|
|
expect(GlobalPerformanceLogger.startTimespan).toHaveBeenCalledWith(
|
|
'network_XMLHttpRequest_blabla',
|
|
);
|
|
expect(GlobalPerformanceLogger.stopTimespan).not.toHaveBeenCalled();
|
|
|
|
setRequestId(8);
|
|
xhr.__didReceiveResponse(requestId, 200, {
|
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
'Content-Length': '32',
|
|
});
|
|
|
|
expect(GlobalPerformanceLogger.stopTimespan).toHaveBeenCalledWith(
|
|
'network_XMLHttpRequest_blabla',
|
|
);
|
|
});
|
|
|
|
it('should log to a custom performance logger if set', () => {
|
|
const performanceLogger = createPerformanceLogger();
|
|
jest.spyOn(performanceLogger, 'startTimespan');
|
|
jest.spyOn(performanceLogger, 'stopTimespan');
|
|
|
|
xhr.setPerformanceLogger(performanceLogger);
|
|
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
|
|
expect(performanceLogger.startTimespan).toHaveBeenCalledWith(
|
|
'network_XMLHttpRequest_blabla',
|
|
);
|
|
expect(GlobalPerformanceLogger.startTimespan).not.toHaveBeenCalled();
|
|
expect(performanceLogger.stopTimespan).not.toHaveBeenCalled();
|
|
|
|
setRequestId(9);
|
|
xhr.__didReceiveResponse(requestId, 200, {
|
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
'Content-Length': '32',
|
|
});
|
|
|
|
expect(performanceLogger.stopTimespan).toHaveBeenCalledWith(
|
|
'network_XMLHttpRequest_blabla',
|
|
);
|
|
expect(GlobalPerformanceLogger.stopTimespan).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should sort and lowercase response headers', function() {
|
|
// Derived from XHR Web Platform Test: https://github.com/web-platform-tests/wpt/blob/master/xhr/getallresponseheaders.htm
|
|
xhr.open('GET', 'blabla');
|
|
xhr.send();
|
|
setRequestId(10);
|
|
xhr.__didReceiveResponse(requestId, 200, {
|
|
'foo-TEST': '1',
|
|
'FOO-test': '2',
|
|
__Custom: 'token',
|
|
'ALSO-here': 'Mr. PB',
|
|
ewok: 'lego',
|
|
});
|
|
|
|
expect(xhr.getAllResponseHeaders()).toBe(
|
|
'also-here: Mr. PB\r\newok: lego\r\nfoo-test: 1, 2\r\n__custom: token\r\n',
|
|
);
|
|
});
|
|
});
|