Skip to content

Commit

Permalink
fix(ajax): Allow XHR to perform body serialization and set content-ty…
Browse files Browse the repository at this point in the history
…pe where possible

- Ajax will now allow default behavior as defined in the specs:
  - https://xhr.spec.whatwg.org/#the-send()-method
  - https://fetch.spec.whatwg.org/#concept-bodyinit-extract
- if the `body` is set to: `Blob`, `ArrayBuffer`, any array buffer view (like a byte sequence, e.g. `Uint8Array`, etc), `FormData`, `URLSearchParams`, `string`, or `ReadableStream`, default handling is use. If the `body` is otherwise `typeof` `"object"`, then it will be converted to JSON via `JSON.stringify`, and the `Content-Type` header will be set to `application/json;charset=utf-8`. All other types will emit an error.
- Updates tests.
- Puts the behavior in-line with Axios, et al.

resolves ReactiveX#2837

BREAKING CHANGE: `ajax` body serialization will now use default XHR behavior in all cases. If the body is a `Blob`, `ArrayBuffer`, any array buffer view (like a byte sequence, e.g. `Uint8Array`, etc), `FormData`, `URLSearchParams`, `string`, or `ReadableStream`, default handling is use. If the `body` is otherwise `typeof` `"object"`, then it will be converted to JSON via `JSON.stringify`, and the `Content-Type` header will be set to `application/json;charset=utf-8`. All other types will emit an error.

BREAKING CHANGE: The `Content-Type` header passed to `ajax` configuration no longer has any effect on the serialization behavior of the AJAX request.
  • Loading branch information
benlesh committed Sep 3, 2020
1 parent 3dfd38f commit 9003dab
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 110 deletions.
47 changes: 47 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"escape-string-regexp": "1.0.5",
"eslint": "4.18.2",
"eslint-plugin-jasmine": "^2.10.1",
"form-data": "^3.0.0",
"fs-extra": "^8.1.0",
"glob": "7.1.2",
"google-closure-compiler-js": "20170218.0.0",
Expand Down
105 changes: 21 additions & 84 deletions spec/observables/dom/ajax-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import * as sinon from 'sinon';
import { ajax, AjaxConfig, AjaxResponse, AjaxError, AjaxTimeoutError } from 'rxjs/ajax';
import { TestScheduler } from 'rxjs/testing';
import { noop } from 'rxjs';
import * as nodeFormData from 'form-data';



const root: any = (typeof globalThis !== 'undefined' && globalThis) || (typeof self !== 'undefined' && self) || global;

if (typeof root.FormData === 'undefined') {
root.FormData = nodeFormData as any;
}

/** @test {ajax} */
describe('ajax', () => {
let rXHR: XMLHttpRequest;
Expand Down Expand Up @@ -495,21 +502,10 @@ describe('ajax', () => {
});

describe('ajax request body', () => {
let rFormData: FormData;

beforeEach(() => {
rFormData = root.FormData;
root.FormData = root.FormData || class {};
});

afterEach(() => {
root.FormData = rFormData;
});

it('can take string body', () => {
const obj = {
url: '/flibbertyJibbet',
method: '',
method: 'POST',
body: 'foobar',
};

Expand All @@ -523,53 +519,33 @@ describe('ajax', () => {
const body = new root.FormData();
const obj = {
url: '/flibbertyJibbet',
method: '',
method: 'POST',
body: body,
};

ajax(obj).subscribe();

expect(MockXMLHttpRequest.mostRecent.url).to.equal('/flibbertyJibbet');
expect(MockXMLHttpRequest.mostRecent.data).to.deep.equal(body);
expect(MockXMLHttpRequest.mostRecent.data).to.equal(body);
expect(MockXMLHttpRequest.mostRecent.requestHeaders).to.deep.equal({
'x-requested-with': 'XMLHttpRequest',
});
});

it('should not fail when FormData is undefined', () => {
root.FormData = void 0;

const obj = {
url: '/flibbertyJibbet',
method: '',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: { '🌟': '🚀' },
};

ajax(obj).subscribe();

expect(MockXMLHttpRequest.mostRecent.url).to.equal('/flibbertyJibbet');
});

it('should send by form-urlencoded format', () => {
const body = {
it('should send the URLSearchParams straight through to the body', () => {
const body = new URLSearchParams({
'🌟': '🚀',
};
});
const obj = {
url: '/flibbertyJibbet',
method: '',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
body: body,
};

ajax(obj).subscribe();

expect(MockXMLHttpRequest.mostRecent.url).to.equal('/flibbertyJibbet');
expect(MockXMLHttpRequest.mostRecent.data).to.equal('%F0%9F%8C%9F=%F0%9F%9A%80');
expect(MockXMLHttpRequest.mostRecent.data).to.equal(body);
});

it('should send by JSON', () => {
Expand All @@ -578,10 +554,7 @@ describe('ajax', () => {
};
const obj = {
url: '/flibbertyJibbet',
method: '',
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: body,
};

Expand All @@ -601,7 +574,7 @@ describe('ajax', () => {
method: '',
body: body,
headers: {
'cOnTeNt-TyPe': 'application/json; charset=UTF-8',
'cOnTeNt-TyPe': 'application/json;charset=UTF-8',
},
};

Expand Down Expand Up @@ -753,42 +726,7 @@ describe('ajax', () => {
expect(request.method).to.equal('POST');
expect(request.url).to.equal('/flibbertyJibbet');
expect(request.requestHeaders).to.deep.equal({
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'x-requested-with': 'XMLHttpRequest',
});

request.respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(expected),
});

expect(request.data).to.equal('foo=bar&hi=there%20you');
expect(result!.response).to.deep.equal(expected);
expect(complete).to.be.true;
});

it('should properly encode full URLs passed', () => {
const expected = { test: 'https://google.com/search?q=encodeURI+vs+encodeURIComponent' };
let result: AjaxResponse<any>;
let complete = false;

ajax.post('/flibbertyJibbet', expected).subscribe(
(x) => {
result = x;
},
null,
() => {
complete = true;
}
);

const request = MockXMLHttpRequest.mostRecent;

expect(request.method).to.equal('POST');
expect(request.url).to.equal('/flibbertyJibbet');
expect(request.requestHeaders).to.deep.equal({
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'content-type': 'application/json;charset=utf-8',
'x-requested-with': 'XMLHttpRequest',
});

Expand All @@ -798,7 +736,7 @@ describe('ajax', () => {
responseText: JSON.stringify(expected),
});

expect(request.data).to.equal('test=https%3A%2F%2Fgoogle.com%2Fsearch%3Fq%3DencodeURI%2Bvs%2BencodeURIComponent');
expect(request.data).to.equal(JSON.stringify(expected));
expect(result!.response).to.deep.equal(expected);
expect(complete).to.be.true;
});
Expand All @@ -823,7 +761,6 @@ describe('ajax', () => {
expect(request.method).to.equal('POST');
expect(request.url).to.equal('/flibbertyJibbet');
expect(request.requestHeaders).to.deep.equal({
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'x-requested-with': 'XMLHttpRequest',
});

Expand Down Expand Up @@ -1043,7 +980,7 @@ describe('ajax', () => {
expect(request.method).to.equal('PATCH');
expect(request.url).to.equal('/flibbertyJibbet');
expect(request.requestHeaders).to.deep.equal({
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'content-type': 'application/json;charset=utf-8',
'x-requested-with': 'XMLHttpRequest',
});

Expand All @@ -1053,7 +990,7 @@ describe('ajax', () => {
responseText: JSON.stringify(expected),
});

expect(request.data).to.equal('foo=bar&hi=there%20you');
expect(request.data).to.equal(JSON.stringify(expected));
expect(result!.response).to.deep.equal(expected);
expect(complete).to.be.true;
});
Expand Down
Loading

0 comments on commit 9003dab

Please sign in to comment.