Skip to content

Commit cb47719

Browse files
authored
fix(xmlhttprequest): support object property (#116)
1 parent f765c4d commit cb47719

File tree

4 files changed

+263
-19
lines changed

4 files changed

+263
-19
lines changed

src/html/eventhandler.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2020 Yamagishi Kazutoshi
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
23+
import Event from '../dom/event';
24+
25+
type EventHandler = (event: Event) => void;
26+
27+
export default EventHandler;

src/xmlhttprequest.ts

+20
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import * as http from 'http';
2424
import * as https from 'https';
2525
import Event from './dom/event';
2626
import FormData from './formdata';
27+
import EventHandler from './html/eventhandler';
2728
import ProgressEvent from './progressevent';
2829
import DOMException from './webidl/domexception';
2930
import XMLHttpRequestEventTarget from './xmlhttprequesteventtarget';
@@ -112,6 +113,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
112113
readonly upload: XMLHttpRequestUpload;
113114

114115
#client: http.ClientRequest | null;
116+
#onreadystatechange: EventHandler | null;
115117
#responseBuffer: Buffer;
116118
#responseHeaders: http.IncomingHttpHeaders | null;
117119

@@ -123,6 +125,23 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
123125
#timeout: number;
124126
#withCredentials: boolean;
125127

128+
get onreadystatechange(): EventHandler | null {
129+
return this.#onreadystatechange;
130+
}
131+
132+
set onreadystatechange(value: EventHandler | null) {
133+
if (this.#onreadystatechange) {
134+
this.removeEventListener('readystatechange', this.#onreadystatechange);
135+
}
136+
137+
if (typeof value === 'function') {
138+
this.#onreadystatechange = value;
139+
this.addEventListener('readystatechange', this.#onreadystatechange);
140+
} else {
141+
this.#onreadystatechange = null;
142+
}
143+
}
144+
126145
get readyState(): number {
127146
return this.#readyState;
128147
}
@@ -211,6 +230,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
211230
this.upload = new XMLHttpRequestUpload();
212231

213232
this.#client = null;
233+
this.#onreadystatechange = null;
214234
this.#responseBuffer = Buffer.alloc(0);
215235
this.#responseHeaders = null;
216236

src/xmlhttprequesteventtarget.ts

+141-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,145 @@
2121
// THE SOFTWARE.
2222

2323
import EventTarget from './dom/eventtarget';
24+
import EventHandler from './html/eventhandler';
2425

25-
export default class XMLHttpRequestEventTarget extends EventTarget {}
26+
export default class XMLHttpRequestEventTarget extends EventTarget {
27+
#onabort: EventHandler | null;
28+
#onerror: EventHandler | null;
29+
#onload: EventHandler | null;
30+
#onloadstart: EventHandler | null;
31+
#onloadend: EventHandler | null;
32+
#onprogress: EventHandler | null;
33+
#ontimeout: EventHandler | null;
34+
35+
get onabort(): EventHandler | null {
36+
return this.#onabort;
37+
}
38+
39+
set onabort(value: EventHandler | null) {
40+
if (this.#onabort) {
41+
this.removeEventListener('abort', this.#onabort);
42+
}
43+
44+
if (typeof value === 'function') {
45+
this.#onabort = value;
46+
this.addEventListener('abort', this.#onabort);
47+
} else {
48+
this.#onabort = null;
49+
}
50+
}
51+
52+
get onerror(): EventHandler | null {
53+
return this.#onerror;
54+
}
55+
56+
set onerror(value: EventHandler | null) {
57+
if (this.#onerror) {
58+
this.removeEventListener('error', this.#onerror);
59+
}
60+
61+
if (typeof value === 'function') {
62+
this.#onerror = value;
63+
this.addEventListener('error', this.#onerror);
64+
} else {
65+
this.#onerror = null;
66+
}
67+
}
68+
69+
get onload(): EventHandler | null {
70+
return this.#onload;
71+
}
72+
73+
set onload(value: EventHandler | null) {
74+
if (this.#onload) {
75+
this.removeEventListener('load', this.#onload);
76+
}
77+
78+
if (typeof value === 'function') {
79+
this.#onload = value;
80+
this.addEventListener('load', this.#onload);
81+
} else {
82+
this.#onload = null;
83+
}
84+
}
85+
86+
get onloadstart(): EventHandler | null {
87+
return this.#onloadstart;
88+
}
89+
90+
set onloadstart(value: EventHandler | null) {
91+
if (this.#onloadstart) {
92+
this.removeEventListener('loadstart', this.#onloadstart);
93+
}
94+
95+
if (typeof value === 'function') {
96+
this.#onloadstart = value;
97+
this.addEventListener('loadstart', this.#onloadstart);
98+
} else {
99+
this.#onloadstart = null;
100+
}
101+
}
102+
103+
get onloadend(): EventHandler | null {
104+
return this.#onloadend;
105+
}
106+
107+
set onloadend(value: EventHandler | null) {
108+
if (this.#onloadend) {
109+
this.removeEventListener('loadend', this.#onloadend);
110+
}
111+
112+
if (typeof value === 'function') {
113+
this.#onloadend = value;
114+
this.addEventListener('loadend', this.#onloadend);
115+
} else {
116+
this.#onloadend = null;
117+
}
118+
}
119+
120+
get onprogress(): EventHandler | null {
121+
return this.#onprogress;
122+
}
123+
124+
set onprogress(value: EventHandler | null) {
125+
if (this.#onprogress) {
126+
this.removeEventListener('progress', this.#onprogress);
127+
}
128+
129+
if (typeof value === 'function') {
130+
this.#onprogress = value;
131+
this.addEventListener('progress', this.#onprogress);
132+
} else {
133+
this.#onprogress = null;
134+
}
135+
}
136+
137+
get ontimeout(): EventHandler | null {
138+
return this.#ontimeout;
139+
}
140+
141+
set ontimeout(value: EventHandler | null) {
142+
if (this.#ontimeout) {
143+
this.removeEventListener('timeout', this.#ontimeout);
144+
}
145+
146+
if (typeof value === 'function') {
147+
this.#ontimeout = value;
148+
this.addEventListener('timeout', this.#ontimeout);
149+
} else {
150+
this.#ontimeout = null;
151+
}
152+
}
153+
154+
constructor() {
155+
super();
156+
157+
this.#onabort = null;
158+
this.#onerror = null;
159+
this.#onload = null;
160+
this.#onloadstart = null;
161+
this.#onloadend = null;
162+
this.#onprogress = null;
163+
this.#ontimeout = null;
164+
}
165+
}

test/integration/xmlhttprequest.ts

+75-18
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('XMLHttpRequest', () => {
6565
baseURL = null;
6666
});
6767

68-
describe('abort()', () => {
68+
describe('.abort()', () => {
6969
it('basic use case', (done) => {
7070
const client = new XMLHttpRequest();
7171

@@ -84,7 +84,7 @@ describe('XMLHttpRequest', () => {
8484
});
8585
});
8686

87-
describe("addEventListener(event: 'error')", () => {
87+
describe(".addEventListener(event: 'error')", () => {
8888
it('basic use case', (done) => {
8989
const client = new XMLHttpRequest();
9090

@@ -97,29 +97,86 @@ describe('XMLHttpRequest', () => {
9797
});
9898
});
9999

100-
describe("addEventListener(event: 'load')", () => {
100+
describe.each([
101+
['loadstart', 0, '', ''],
102+
['progress', 200, 'OK', 'response text'],
103+
['load', 200, 'OK', 'response text'],
104+
['loadend', 200, 'OK', 'response text']
105+
])(
106+
`.addEventListener(event: '%s')`,
107+
(eventType, expectedStatus, expectedStatusText, expectedResponseText) => {
108+
it('basic use case', (done) => {
109+
const client = new XMLHttpRequest();
110+
111+
client.addEventListener(eventType, () => {
112+
expect(client.status).toBe(expectedStatus);
113+
expect(client.statusText).toBe(expectedStatusText);
114+
115+
if (eventType !== 'progress') {
116+
expect(client.responseText).toBe(expectedResponseText);
117+
}
118+
119+
done();
120+
});
121+
122+
client.open('GET', `${baseURL}/?body=response%20text`);
123+
client.send(null);
124+
});
125+
126+
it('use object property', (done) => {
127+
const client = new XMLHttpRequest();
128+
const prop = `on${eventType}` as
129+
| 'onload'
130+
| 'onloadend'
131+
| 'onloadstart'
132+
| 'onprogress';
133+
134+
client[prop] = () => {
135+
expect(client.status).toBe(expectedStatus);
136+
expect(client.statusText).toBe(expectedStatusText);
137+
expect(client.responseText).toBe(expectedResponseText);
138+
139+
done();
140+
};
141+
142+
client.open('GET', `${baseURL}/?body=response%20text`);
143+
client.send(null);
144+
});
145+
}
146+
);
147+
148+
describe(".addEventListener(event: 'readystatechange')", () => {
101149
it('basic use case', (done) => {
102150
const client = new XMLHttpRequest();
151+
const states = new Set<number>([client.readyState]);
103152

104-
client.addEventListener('load', () => {
105-
expect(client.status).toBe(200);
106-
expect(client.statusText).toBe('OK');
107-
expect(client.responseText).toBe('response text');
153+
client.addEventListener('readystatechange', () => {
154+
states.add(client.readyState);
108155

109-
done();
156+
if (client.readyState === XMLHttpRequest.DONE) {
157+
expect(states).toEqual(
158+
new Set([
159+
XMLHttpRequest.UNSENT,
160+
XMLHttpRequest.OPENED,
161+
XMLHttpRequest.HEADERS_RECEIVED,
162+
XMLHttpRequest.LOADING,
163+
XMLHttpRequest.DONE
164+
])
165+
);
166+
167+
done();
168+
}
110169
});
111170

112-
client.open('GET', `${baseURL}/?body=response%20text`);
171+
client.open('GET', `${baseURL}/?body=onreadystatechange`);
113172
client.send(null);
114173
});
115-
});
116174

117-
describe("addEventListener(event: 'readystatechange')", () => {
118-
it('returns all states', (done) => {
175+
it('use object property', (done) => {
119176
const client = new XMLHttpRequest();
120177
const states = new Set<number>([client.readyState]);
121178

122-
client.addEventListener('readystatechange', () => {
179+
client.onreadystatechange = () => {
123180
states.add(client.readyState);
124181

125182
if (client.readyState === XMLHttpRequest.DONE) {
@@ -135,14 +192,14 @@ describe('XMLHttpRequest', () => {
135192

136193
done();
137194
}
138-
});
195+
};
139196

140197
client.open('GET', `${baseURL}/?body=onreadystatechange`);
141198
client.send(null);
142199
});
143200
});
144201

145-
describe('getAllResponseHeaders()', () => {
202+
describe('.getAllResponseHeaders()', () => {
146203
it('returns all response headers', (done) => {
147204
const client = new XMLHttpRequest();
148205

@@ -166,7 +223,7 @@ describe('XMLHttpRequest', () => {
166223
});
167224
});
168225

169-
describe('getResponseHeader()', () => {
226+
describe('.getResponseHeader()', () => {
170227
it('returns response header value', (done) => {
171228
const client = new XMLHttpRequest();
172229

@@ -181,7 +238,7 @@ describe('XMLHttpRequest', () => {
181238
});
182239
});
183240

184-
describe('responseText', () => {
241+
describe('.responseText', () => {
185242
it('returns object when given JSON', (done) => {
186243
const client = new XMLHttpRequest();
187244

@@ -203,7 +260,7 @@ describe('XMLHttpRequest', () => {
203260
});
204261
});
205262

206-
describe('#withCredentials', () => {
263+
describe('.withCredentials', () => {
207264
it('throws InvalidStateError when readyState is DONE', (done) => {
208265
const client = new XMLHttpRequest();
209266

0 commit comments

Comments
 (0)