From 350a25a3d60759f5eb2197c9126a6f95d2ee2b28 Mon Sep 17 00:00:00 2001 From: Stefan Hoelzl <1478183+stefanhoelzl@users.noreply.github.com> Date: Sun, 16 Oct 2022 09:48:59 +0000 Subject: [PATCH] #582@patch: Window.fetch handles URL and Request objects. --- packages/happy-dom/src/fetch/FetchHandler.ts | 23 +++++++++- packages/happy-dom/src/fetch/IRequest.ts | 3 ++ packages/happy-dom/src/window/IWindow.ts | 5 ++- packages/happy-dom/src/window/Window.ts | 5 ++- packages/happy-dom/test/setup.js | 11 ++++- packages/happy-dom/test/window/Window.test.ts | 44 ++++++++++++++++++- 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/packages/happy-dom/src/fetch/FetchHandler.ts b/packages/happy-dom/src/fetch/FetchHandler.ts index 81fe86b57..43daa9aec 100644 --- a/packages/happy-dom/src/fetch/FetchHandler.ts +++ b/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. @@ -17,14 +20,30 @@ export default class FetchHandler { * @param [init] Init. * @returns Response. */ - public static fetch(document: IDocument, url: string, init?: IRequestInit): Promise { + public static fetch( + document: IDocument, + url: RequestInfo, + init?: IRequestInit + ): Promise { // 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.')); diff --git a/packages/happy-dom/src/fetch/IRequest.ts b/packages/happy-dom/src/fetch/IRequest.ts index 7c35aeb9f..ad907a73f 100644 --- a/packages/happy-dom/src/fetch/IRequest.ts +++ b/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. diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 3d9a61eb9..7e60865e4 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -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'; @@ -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; + fetch(url: RequestInfo, init?: IRequestInit): Promise; /** * 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). diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 5120800a6..c710fff63 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -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'; @@ -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 { + public async fetch(url: RequestInfo, init?: IRequestInit): Promise { return await FetchHandler.fetch(this.document, url, init); } diff --git a/packages/happy-dom/test/setup.js b/packages/happy-dom/test/setup.js index bc51b5c89..ee7bdeb7c 100644 --- a/packages/happy-dom/test/setup.js +++ b/packages/happy-dom/test/setup.js @@ -5,7 +5,9 @@ global.mockedModules = { options: null }, 'node-fetch': { - url: null, + url: { + url: Symbol('url') + }, init: null, error: null, response: { @@ -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', () => { diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 8c8a92837..e531682cd 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -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]); }); @@ -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); });