Skip to content

Commit

Permalink
capricorn86#582@patch: Window.fetch handles URL and Request objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanhoelzl committed Oct 16, 2022
1 parent 414e6ab commit 350a25a
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 10 deletions.
23 changes: 21 additions & 2 deletions packages/happy-dom/src/fetch/FetchHandler.ts
@@ -1,9 +1,12 @@
import URL from '../location/URL';
import RelativeURL from '../location/RelativeURL';
import { RequestInfo } from './IRequest';
import IRequestInit from './IRequestInit';
import IDocument from '../nodes/document/IDocument';
import IResponse from './IResponse';
import Response from './Response';
import NodeFetch from 'node-fetch';
import { Request } from 'node-fetch';

/**
* Helper class for performing fetch.
Expand All @@ -17,14 +20,30 @@ export default class FetchHandler {
* @param [init] Init.
* @returns Response.
*/
public static fetch(document: IDocument, url: string, init?: IRequestInit): Promise<IResponse> {
public static fetch(
document: IDocument,
url: RequestInfo,
init?: IRequestInit
): Promise<IResponse> {
// We want to only load NodeFetch when it is needed to improve performance and not have direct dependencies to server side packages.
const taskManager = document.defaultView.happyDOM.asyncTaskManager;

return new Promise((resolve, reject) => {
const taskID = taskManager.startTask();

NodeFetch(RelativeURL.getAbsoluteURL(document.defaultView.location, url), init)
let request;
if (typeof url === 'string') {
request = new Request(RelativeURL.getAbsoluteURL(document.defaultView.location, url));
} else if (url instanceof URL) {
// URLs are always absolute, no need for getAbsoluteURL.
request = new Request(url);
} else {
request = new Request(RelativeURL.getAbsoluteURL(document.defaultView.location, url.url), {
...url
});
}

NodeFetch(request, init)
.then((response) => {
if (taskManager.getTaskCount() === 0) {
reject(new Error('Failed to complete fetch request. Task was canceled.'));
Expand Down
3 changes: 3 additions & 0 deletions packages/happy-dom/src/fetch/IRequest.ts
@@ -1,5 +1,8 @@
import IHeaders from './IHeaders';
import IBody from './IBody';
import URL from './../location/URL';

export type RequestInfo = IRequest | string | URL;

/**
* Fetch request.
Expand Down
5 changes: 3 additions & 2 deletions packages/happy-dom/src/window/IWindow.ts
Expand Up @@ -87,6 +87,7 @@ import Plugin from '../navigator/Plugin';
import PluginArray from '../navigator/PluginArray';
import IResponseInit from '../fetch/IResponseInit';
import IRequest from '../fetch/IRequest';
import { RequestInfo } from '../fetch/IRequest';
import IHeaders from '../fetch/IHeaders';
import IRequestInit from '../fetch/IRequestInit';
import IResponse from '../fetch/IResponse';
Expand Down Expand Up @@ -328,11 +329,11 @@ export default interface IWindow extends IEventTarget, NodeJS.Global {
/**
* This method provides an easy, logical way to fetch resources asynchronously across the network.
*
* @param url URL.
* @param url RequestInfo.
* @param [init] Init.
* @returns Promise.
*/
fetch(url: string, init?: IRequestInit): Promise<IResponse>;
fetch(url: RequestInfo, init?: IRequestInit): Promise<IResponse>;

/**
* Creates a Base64-encoded ASCII string from a binary string (i.e., a string in which each character in the string is treated as a byte of binary data).
Expand Down
5 changes: 3 additions & 2 deletions packages/happy-dom/src/window/Window.ts
Expand Up @@ -80,6 +80,7 @@ import AsyncTaskManager from '../async-task-manager/AsyncTaskManager';
import IResponse from '../fetch/IResponse';
import IResponseInit from '../fetch/IResponseInit';
import IRequest from '../fetch/IRequest';
import { RequestInfo } from '../fetch/IRequest';
import IRequestInit from '../fetch/IRequestInit';
import IHeaders from '../fetch/IHeaders';
import IHeadersInit from '../fetch/IHeadersInit';
Expand Down Expand Up @@ -615,11 +616,11 @@ export default class Window extends EventTarget implements IWindow {
* This method provides an easy, logical way to fetch resources asynchronously across the network.
*
* @override
* @param url URL.
* @param url RequestInfo.
* @param [init] Init.
* @returns Promise.
*/
public async fetch(url: string, init?: IRequestInit): Promise<IResponse> {
public async fetch(url: RequestInfo, init?: IRequestInit): Promise<IResponse> {
return await FetchHandler.fetch(this.document, url, init);
}

Expand Down
11 changes: 9 additions & 2 deletions packages/happy-dom/test/setup.js
Expand Up @@ -5,7 +5,9 @@ global.mockedModules = {
options: null
},
'node-fetch': {
url: null,
url: {
url: Symbol('url')
},
init: null,
error: null,
response: {
Expand Down Expand Up @@ -53,7 +55,12 @@ class NodeFetchResponse {
}
}

class NodeFetchRequest extends NodeFetchResponse {}
class NodeFetchRequest extends NodeFetchResponse {
constructor(url) {
super();
this.url = url;
}
}
class NodeFetchHeaders {}

jest.mock('node-fetch', () => {
Expand Down
44 changes: 42 additions & 2 deletions packages/happy-dom/test/window/Window.test.ts
Expand Up @@ -455,7 +455,7 @@ describe('Window', () => {
const response = await window.fetch(expectedUrl, expectedOptions);
const result = await response[method]();

expect(MOCKED_NODE_FETCH.url).toBe(expectedUrl);
expect(MOCKED_NODE_FETCH.url.url).toBe(expectedUrl);
expect(MOCKED_NODE_FETCH.init).toBe(expectedOptions);
expect(result).toEqual(MOCKED_NODE_FETCH.response[method]);
});
Expand All @@ -470,7 +470,47 @@ describe('Window', () => {
const response = await window.fetch(expectedPath, expectedOptions);
const textResponse = await response.text();

expect(MOCKED_NODE_FETCH.url).toBe('https://localhost:8080' + expectedPath);
expect(MOCKED_NODE_FETCH.url.url).toBe('https://localhost:8080' + expectedPath);
expect(MOCKED_NODE_FETCH.init).toBe(expectedOptions);
expect(textResponse).toEqual(MOCKED_NODE_FETCH.response.text);
});

it('Handles URL object.', async () => {
const expectedUrl = new window.URL('https://localhost:8080/path');
const expectedOptions = {};

const response = await window.fetch(expectedUrl, expectedOptions);
const textResponse = await response.text();

expect(MOCKED_NODE_FETCH.url.url).toBe(expectedUrl);
expect(MOCKED_NODE_FETCH.init).toBe(expectedOptions);
expect(textResponse).toEqual(MOCKED_NODE_FETCH.response.text);
});

it('Handles Request object.', async () => {
const expectedRequest = new window.Request('https://localhost:8080/path', { method: 'GET' });
const expectedOptions = {};

const response = await window.fetch(expectedRequest, expectedOptions);
const textResponse = await response.text();

expect(MOCKED_NODE_FETCH.url.url).toBe(expectedRequest.url);
expect(MOCKED_NODE_FETCH.url.method).toBe(expectedRequest.method);
expect(MOCKED_NODE_FETCH.init).toBe(expectedOptions);
expect(textResponse).toEqual(MOCKED_NODE_FETCH.response.text);
});

it('Handles Request object with relative url.', async () => {
const expectedPath = '/path';
const expectedRequest = new window.Request(expectedPath);
const expectedOptions = {};

window.location.href = 'https://localhost:8080';

const response = await window.fetch(expectedRequest, expectedOptions);
const textResponse = await response.text();

expect(MOCKED_NODE_FETCH.url.url).toBe('https://localhost:8080' + expectedPath);
expect(MOCKED_NODE_FETCH.init).toBe(expectedOptions);
expect(textResponse).toEqual(MOCKED_NODE_FETCH.response.text);
});
Expand Down

0 comments on commit 350a25a

Please sign in to comment.