Skip to content

Commit 6ba374e

Browse files
danmarshalljayphelps
authored andcommitted
fix(AjaxObservable): return null value from JSON.Parse (#1904)
* Return null value from JSON.Parse JSON.parse('') will throw a syntax error (in any browser). Fix is to return a null value. I saw this when my server returned HTTP 204 No Content. * added ajax tests for 204 No Content * mock the behavior of IE unit test for IE * pascal case class name. added missing semicolon. * Use 'null' instead of '' for JSON.parse * remove empty constructor * fix(AjaxObservable): return null value from JSON.Parse JSON.parse('') will throw a syntax error (in any browser). Fix is to return a null value. This happens during an HTTP 204 'No Content' in IE. Added a mock for InternetExplorer. Added unit tests for HTTP 204. closes #1381
1 parent 990594b commit 6ba374e

File tree

3 files changed

+144
-14
lines changed

3 files changed

+144
-14
lines changed

spec/helpers/ajax-helper.ts

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,17 @@ export class MockXMLHttpRequest {
117117

118118
private previousRequest: MockXMLHttpRequest;
119119

120-
private responseType: string = '';
120+
protected responseType: string = '';
121121
private eventHandlers: Array<any> = [];
122122
private readyState: number = 0;
123123

124124
private user: any;
125125
private password: any;
126126

127127
private responseHeaders: any;
128-
private status: any;
129-
private responseText: string;
130-
private response: any;
128+
protected status: any;
129+
protected responseText: string;
130+
protected response: any;
131131

132132
url: any;
133133
method: any;
@@ -176,6 +176,18 @@ export class MockXMLHttpRequest {
176176
this.triggerEvent('error');
177177
}
178178

179+
protected jsonResponseValue(response: any) {
180+
try {
181+
this.response = JSON.parse(response.responseText);
182+
} catch (err) {
183+
throw new Error('unable to JSON.parse: \n' + response.responseText);
184+
}
185+
}
186+
187+
protected defaultResponseValue() {
188+
throw new Error('unhandled type "' + this.responseType + '"');
189+
}
190+
179191
respondWith(response: any): void {
180192
this.readyState = 4;
181193
this.responseHeaders = {
@@ -186,17 +198,13 @@ export class MockXMLHttpRequest {
186198
if (!('response' in response)) {
187199
switch (this.responseType) {
188200
case 'json':
189-
try {
190-
this.response = JSON.parse(response.responseText);
191-
} catch (err) {
192-
throw new Error('unable to JSON.parse: \n' + response.responseText);
193-
}
201+
this.jsonResponseValue(response);
194202
break;
195203
case 'text':
196204
this.response = response.responseText;
197205
break;
198206
default:
199-
throw new Error('unhandled type "' + this.responseType + '"');
207+
this.defaultResponseValue();
200208
}
201209
}
202210
// TODO: pass better event to onload.
@@ -218,4 +226,30 @@ export class MockXMLHttpRequest {
218226
}
219227
});
220228
}
221-
}
229+
}
230+
231+
export class MockXMLHttpRequestInternetExplorer extends MockXMLHttpRequest {
232+
233+
private mockHttp204() {
234+
this.responseType = '';
235+
this.responseText = '';
236+
this.response = '';
237+
}
238+
239+
protected jsonResponseValue(response: any) {
240+
if (this.status == 204) {
241+
this.mockHttp204();
242+
return;
243+
}
244+
return super.jsonResponseValue(response);
245+
}
246+
247+
protected defaultResponseValue() {
248+
if (this.status == 204) {
249+
this.mockHttp204();
250+
return;
251+
}
252+
return super.defaultResponseValue();
253+
}
254+
255+
}

spec/observables/dom/ajax-spec.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {expect} from 'chai';
22
import * as sinon from 'sinon';
33
import * as Rx from '../../../dist/cjs/Rx';
44
import {root} from '../../../dist/cjs/util/root';
5-
import {MockXMLHttpRequest} from '../../helpers/ajax-helper';
5+
import {MockXMLHttpRequest, MockXMLHttpRequestInternetExplorer} from '../../helpers/ajax-helper';
66

77
declare const global: any;
88

@@ -440,6 +440,33 @@ describe('Observable.ajax', () => {
440440
expect(complete).to.be.true;
441441
});
442442

443+
it('should succeed on 204 No Content', () => {
444+
const expected = null;
445+
let result;
446+
let complete = false;
447+
448+
Rx.Observable
449+
.ajax.get('/flibbertyJibbet')
450+
.subscribe(x => {
451+
result = x.response;
452+
}, null, () => {
453+
complete = true;
454+
});
455+
456+
const request = MockXMLHttpRequest.mostRecent;
457+
458+
expect(request.url).to.equal('/flibbertyJibbet');
459+
460+
request.respondWith({
461+
'status': 204,
462+
'contentType': 'application/json',
463+
'responseText': expected
464+
});
465+
466+
expect(result).to.deep.equal(expected);
467+
expect(complete).to.be.true;
468+
});
469+
443470
it('should able to select json response via getJSON', () => {
444471
const expected = { foo: 'bar' };
445472
let result;
@@ -469,6 +496,7 @@ describe('Observable.ajax', () => {
469496
});
470497

471498
describe('ajax.post', () => {
499+
472500
it('should succeed on 200', () => {
473501
const expected = { foo: 'bar', hi: 'there you' };
474502
let result: Rx.AjaxResponse;
@@ -501,5 +529,73 @@ describe('Observable.ajax', () => {
501529
expect(result.response).to.deep.equal(expected);
502530
expect(complete).to.be.true;
503531
});
532+
533+
it('should succeed on 204 No Content', () => {
534+
const expected = null;
535+
let result: Rx.AjaxResponse;
536+
let complete = false;
537+
538+
Rx.Observable
539+
.ajax.post('/flibbertyJibbet', expected)
540+
.subscribe(x => {
541+
result = x;
542+
}, null, () => {
543+
complete = true;
544+
});
545+
546+
const request = MockXMLHttpRequest.mostRecent;
547+
548+
expect(request.method).to.equal('POST');
549+
expect(request.url).to.equal('/flibbertyJibbet');
550+
expect(request.requestHeaders).to.deep.equal({
551+
'X-Requested-With': 'XMLHttpRequest',
552+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
553+
});
554+
555+
request.respondWith({
556+
'status': 204,
557+
'contentType': 'application/json',
558+
'responseType': 'json',
559+
'responseText': expected
560+
});
561+
562+
expect(result.response).to.equal(expected);
563+
expect(complete).to.be.true;
564+
});
565+
566+
it('should succeed in IE on 204 No Content', () => {
567+
const expected = null;
568+
let result: Rx.AjaxResponse;
569+
let complete = false;
570+
571+
root.XMLHttpRequest = MockXMLHttpRequestInternetExplorer;
572+
573+
Rx.Observable
574+
.ajax.post('/flibbertyJibbet', expected)
575+
.subscribe(x => {
576+
result = x;
577+
}, null, () => {
578+
complete = true;
579+
});
580+
581+
const request = MockXMLHttpRequest.mostRecent;
582+
583+
expect(request.method).to.equal('POST');
584+
expect(request.url).to.equal('/flibbertyJibbet');
585+
expect(request.requestHeaders).to.deep.equal({
586+
'X-Requested-With': 'XMLHttpRequest',
587+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
588+
});
589+
590+
//IE behavior: IE does not provide the a responseText property, so also exercise the code which handles this.
591+
request.respondWith({
592+
'status': 204,
593+
'contentType': 'application/json'
594+
});
595+
596+
expect(result.response).to.equal(expected);
597+
expect(complete).to.be.true;
598+
});
599+
504600
});
505601
});

src/observable/dom/AjaxObservable.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,9 @@ export class AjaxResponse {
396396
case 'json':
397397
if ('response' in xhr) {
398398
//IE does not support json as responseType, parse it internally
399-
this.response = xhr.responseType ? xhr.response : JSON.parse(xhr.response || xhr.responseText || '');
399+
this.response = xhr.responseType ? xhr.response : JSON.parse(xhr.response || xhr.responseText || 'null');
400400
} else {
401-
this.response = JSON.parse(xhr.responseText || '');
401+
this.response = JSON.parse(xhr.responseText || 'null');
402402
}
403403
break;
404404
case 'xml':

0 commit comments

Comments
 (0)