Skip to content

Commit ff5d0ff

Browse files
ArnoSaineMichaelDeBoey
authored andcommitted
fix: Allow subclass methods of Headers to be called correctly
1 parent 32e85f4 commit ff5d0ff

File tree

3 files changed

+102
-50
lines changed

3 files changed

+102
-50
lines changed

Diff for: .changeset/hot-foxes-perform.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/web-fetch": patch
3+
---
4+
5+
fix: Allow subclass methods of Headers to be called correctly

Diff for: packages/fetch/src/headers.js

+67-50
Original file line numberDiff line numberDiff line change
@@ -104,55 +104,6 @@ export default class Headers extends URLSearchParams {
104104
[];
105105

106106
super(result);
107-
108-
// Returning a Proxy that will lowercase key names, validate parameters and sort keys
109-
// eslint-disable-next-line no-constructor-return
110-
return new Proxy(this, {
111-
get(target, p, receiver) {
112-
switch (p) {
113-
case 'append':
114-
case 'set':
115-
/**
116-
* @param {string} name
117-
* @param {string} value
118-
*/
119-
return (name, value) => {
120-
validateHeaderName(name);
121-
validateHeaderValue(name, String(value));
122-
return URLSearchParams.prototype[p].call(
123-
target,
124-
String(name).toLowerCase(),
125-
String(value)
126-
);
127-
};
128-
129-
case 'delete':
130-
case 'has':
131-
case 'getAll':
132-
/**
133-
* @param {string} name
134-
*/
135-
return name => {
136-
validateHeaderName(name);
137-
// @ts-ignore
138-
return URLSearchParams.prototype[p].call(
139-
target,
140-
String(name).toLowerCase()
141-
);
142-
};
143-
144-
case 'keys':
145-
return () => {
146-
target.sort();
147-
return new Set(URLSearchParams.prototype.keys.call(target)).keys();
148-
};
149-
150-
default:
151-
return Reflect.get(target, p, receiver);
152-
}
153-
}
154-
/* c8 ignore next */
155-
});
156107
}
157108

158109
get [Symbol.toStringTag]() {
@@ -181,6 +132,56 @@ export default class Headers extends URLSearchParams {
181132
return value;
182133
}
183134

135+
/**
136+
* @param {string} name
137+
*/
138+
getAll(name) {
139+
validateHeaderName(name);
140+
return super.getAll(String(name).toLowerCase());
141+
}
142+
143+
/**
144+
* @param {string} name
145+
* @param {string} value
146+
*/
147+
append(name, value) {
148+
validateHeaderName(name);
149+
validateHeaderValue(name, String(value));
150+
return super.append(
151+
String(name).toLowerCase(),
152+
String(value)
153+
);
154+
}
155+
156+
/**
157+
* @param {string} name
158+
*/
159+
delete(name) {
160+
validateHeaderName(name);
161+
return super.delete(String(name).toLowerCase());
162+
}
163+
164+
/**
165+
* @param {string} name
166+
*/
167+
has(name) {
168+
validateHeaderName(name);
169+
return super.has(String(name).toLowerCase());
170+
}
171+
172+
/**
173+
* @param {string} name
174+
* @param {string} value
175+
*/
176+
set(name, value) {
177+
validateHeaderName(name);
178+
validateHeaderValue(name, String(value));
179+
return super.set(
180+
String(name).toLowerCase(),
181+
String(value)
182+
);
183+
}
184+
184185
/**
185186
* @param {(value: string, key: string, parent: this) => void} callback
186187
* @param {any} thisArg
@@ -199,6 +200,11 @@ export default class Headers extends URLSearchParams {
199200
}
200201
}
201202

203+
keys() {
204+
this.sort();
205+
return new Set(super.keys()).keys();
206+
}
207+
202208
/**
203209
* @returns {IterableIterator<string>}
204210
*/
@@ -272,7 +278,18 @@ export default class Headers extends URLSearchParams {
272278
*/
273279
Object.defineProperties(
274280
Headers.prototype,
275-
['get', 'entries', 'forEach', 'values'].reduce((result, property) => {
281+
[
282+
'append',
283+
'delete',
284+
'entries',
285+
'forEach',
286+
'get',
287+
'getAll',
288+
'has',
289+
'keys',
290+
'set',
291+
'values'
292+
].reduce((result, property) => {
276293
result[property] = {enumerable: true};
277294
return result;
278295
}, /** @type {Record<string, {enumerable:true}>} */ ({}))

Diff for: packages/fetch/test/headers.js

+30
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,34 @@ describe('Headers', () => {
347347
// eslint-disable-next-line quotes
348348
expect(util.format(headers)).to.equal("{ a: [ '1', '3' ], b: '2', host: 'thehost' }");
349349
});
350+
351+
it('should have the correct prototype chain', () => {
352+
const headers = new Headers();
353+
354+
expect(headers).to.be.instanceOf(Headers);
355+
expect(Object.getPrototypeOf(headers)).to.equal(Headers.prototype);
356+
});
357+
358+
it('should have the correct prototype chain when extended', () => {
359+
class MyHeaders extends Headers {}
360+
361+
const headers = new MyHeaders();
362+
363+
expect(headers).to.be.instanceOf(MyHeaders);
364+
expect(headers).to.be.instanceOf(Headers);
365+
expect(Object.getPrototypeOf(headers)).to.equal(MyHeaders.prototype);
366+
});
367+
368+
it('should call the method of the subclass', () => {
369+
class MyHeaders extends Headers {
370+
append(_name, _value) {
371+
return 'subclass method called';
372+
}
373+
}
374+
375+
const headers = new MyHeaders();
376+
const result = headers.append('Content-Type', 'application/json');
377+
378+
expect(result).to.equal('subclass method called');
379+
});
350380
});

0 commit comments

Comments
 (0)