Skip to content

Commit

Permalink
Merge pull request #1143 from capricorn86/task/309-add-support-for-be…
Browse files Browse the repository at this point in the history
…acon

Task/309 add support for beacon
  • Loading branch information
capricorn86 authored Oct 26, 2023
2 parents 0fd9229 + d91e937 commit f7dab33
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
tmp
lerna-debug.log
.DS_Store
.vscode
.idea
.turbo
20 changes: 20 additions & 0 deletions packages/happy-dom/src/navigator/Navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PluginArray from './PluginArray.js';
import IWindow from '../window/IWindow.js';
import Permissions from '../permissions/Permissions.js';
import Clipboard from '../clipboard/Clipboard.js';
import Blob from '../file/Blob.js';
import FormData from '../form-data/FormData.js';

/**
* Browser Navigator API.
Expand Down Expand Up @@ -211,6 +213,24 @@ export default class Navigator {
return new PluginArray([]);
}

/**
* Sends an HTTP POST request containing a small amount of data to a web server.
*
* @param url URL.
* @param data Data.
* @returns "true" if the user agent successfully queued the data for transfer. Otherwise, it returns "false".
*/
public sendBeacon(
url: string,
data: string | Blob | ArrayBuffer | ArrayBufferView | FormData
): boolean {
this.#ownerWindow.fetch(url, {
method: 'POST',
body: data
});
return true;
}

/**
* Returns the object as a string.
*
Expand Down
3 changes: 2 additions & 1 deletion packages/happy-dom/src/window/IWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ import ClipboardItem from '../clipboard/ClipboardItem.js';
import ClipboardEvent from '../event/events/ClipboardEvent.js';

/**
* Window without dependencies to server side specific packages.
* Browser window.
*/
export default interface IWindow extends IEventTarget, INodeJSGlobal {
// Happy DOM property.
Expand Down Expand Up @@ -384,6 +384,7 @@ export default interface IWindow extends IEventTarget, INodeJSGlobal {
readonly DOMParser: typeof DOMParser;
readonly MutationObserver: typeof MutationObserver;
readonly MutationRecord: typeof MutationRecord;
readonly CSSStyleDeclaration: typeof CSSStyleDeclaration;

// Events
onload: (event: Event) => void;
Expand Down
1 change: 1 addition & 0 deletions packages/happy-dom/src/window/Window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ export default class Window extends EventTarget implements IWindow {
public readonly Image;
public readonly DocumentFragment;
public readonly Audio;
public readonly CSSStyleDeclaration = CSSStyleDeclaration;

// Events
public onload: (event: Event) => void = null;
Expand Down
110 changes: 110 additions & 0 deletions packages/happy-dom/test/navigator/Navigator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import Window from '../../src/window/Window.js';
import IWindow from '../../src/window/IWindow.js';
import Navigator from '../../src/navigator/Navigator.js';
import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
import Permissions from '../../src/permissions/Permissions.js';
import Clipboard from '../../src/clipboard/Clipboard.js';
import PackageVersion from '../../src/version.js';
import IResponse from '../../src/fetch/types/IResponse.js';
import IRequest from '../../src/fetch/types/IRequest.js';
import Fetch from '../../src/fetch/Fetch.js';
import Stream from 'stream';

const GET_NAVIGATOR_PLATFORM = (): string => {
return (
'X11; ' +
process.platform.charAt(0).toUpperCase() +
process.platform.slice(1) +
' ' +
process.arch
);
};
const PROPERTIES = {
appCodeName: 'Mozilla',
appName: 'Netscape',
appVersion: `5.0 (${GET_NAVIGATOR_PLATFORM()}) AppleWebKit/537.36 (KHTML, like Gecko) HappyDOM/${
PackageVersion.version
}`,
cookieEnabled: true,
credentials: null,
doNotTrack: 'unspecified',
geolocation: null,
hardwareConcurrency: 8,
language: 'en-US',
languages: ['en-US', 'en'],
locks: null,
maxTouchPoints: 0,
mimeTypes: {
length: 0
},
onLine: true,
permissions: new Permissions(),
platform: GET_NAVIGATOR_PLATFORM(),
plugins: {
length: 0
},
product: 'Gecko',
productSub: '20100101',
userAgent: `Mozilla/5.0 (${GET_NAVIGATOR_PLATFORM()}) AppleWebKit/537.36 (KHTML, like Gecko) HappyDOM/${
PackageVersion.version
}`,
vendor: '',
vendorSub: '',
webdriver: true
};

describe('Window', () => {
let window: IWindow;

beforeEach(() => {
window = new Window();
});

afterEach(() => {
resetMockedModules();
vi.restoreAllMocks();
});

describe('constructor()', () => {
it('Is instanceof Navigator.', () => {
expect(window.navigator instanceof Navigator).toBe(true);
});
});

Object.keys(PROPERTIES).forEach((property) => {
describe(`get ${property}()`, () => {
it('Returns an instance of Navigator with browser data.', () => {
expect(window.navigator[property]).toEqual(PROPERTIES[property]);
});
});
});

describe('get clipboard()', () => {
it('Returns an instance of Clipboard.', () => {
expect(window.navigator.clipboard).toEqual(new Clipboard(window));
});
});

describe('sendBeacon()', () => {
it('Sends a beacon request.', async () => {
const expectedURL = 'https://localhost:8080/path/';
let request: IRequest | null = null;

vi.spyOn(Fetch.prototype, 'send').mockImplementation(function (): Promise<IResponse> {
request = <IRequest>this.request;
return Promise.resolve(<IResponse>{});
});

window.navigator.sendBeacon(expectedURL, 'test-data');

const chunks: Buffer[] = [];

for await (const chunk of <Stream.Readable>(<IRequest>(<unknown>request)).body) {
chunks.push(Buffer.from(chunk));
}

expect(Buffer.concat(chunks).toString()).toBe('test-data');
expect((<IRequest>(<unknown>request)).url).toBe(expectedURL);
});
});
});
50 changes: 2 additions & 48 deletions packages/happy-dom/test/window/Window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import ResourceFetch from '../../src/fetch/ResourceFetch.js';
import IHTMLScriptElement from '../../src/nodes/html-script-element/IHTMLScriptElement.js';
import Window from '../../src/window/Window.js';
import IWindow from '../../src/window/IWindow.js';
import Navigator from '../../src/navigator/Navigator.js';
import Headers from '../../src/fetch/Headers.js';
import Selection from '../../src/selection/Selection.js';
import DOMException from '../../src/exception/DOMException.js';
Expand All @@ -26,8 +25,6 @@ import '../types.d.js';
import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
import VirtualConsole from '../../src/console/VirtualConsole.js';
import VirtualConsolePrinter from '../../src/console/VirtualConsolePrinter.js';
import Permissions from '../../src/permissions/Permissions.js';
import Clipboard from '../../src/clipboard/Clipboard.js';
import PackageVersion from '../../src/version.js';
import { IHTMLDialogElement } from '../../src/index.js';

Expand Down Expand Up @@ -455,49 +452,6 @@ describe('Window', () => {
});
});

describe('get navigator()', () => {
it('Returns an instance of Navigator with browser data.', () => {
const platform = GET_NAVIGATOR_PLATFORM();

expect(window.navigator instanceof Navigator).toBe(true);

const referenceValues = {
appCodeName: 'Mozilla',
appName: 'Netscape',
appVersion: `5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) HappyDOM/${PackageVersion.version}`,
cookieEnabled: true,
credentials: null,
doNotTrack: 'unspecified',
geolocation: null,
hardwareConcurrency: 8,
language: 'en-US',
languages: ['en-US', 'en'],
locks: null,
maxTouchPoints: 0,
mimeTypes: {
length: 0
},
onLine: true,
permissions: new Permissions(),
clipboard: new Clipboard(window),
platform,
plugins: {
length: 0
},
product: 'Gecko',
productSub: '20100101',
userAgent: `Mozilla/5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) HappyDOM/${PackageVersion.version}`,
vendor: '',
vendorSub: '',
webdriver: true
};

for (const propertyKey in referenceValues) {
expect(window.navigator[propertyKey]).toEqual(referenceValues[propertyKey]);
}
});
});

describe('eval()', () => {
it('Respects direct eval.', () => {
const result = window.eval(`
Expand Down Expand Up @@ -1448,7 +1402,7 @@ describe('Window', () => {

(<Window>window.parent) = parent;

window.addEventListener('message', (event) => (triggeredEvent = event));
window.addEventListener('message', (event) => (triggeredEvent = <MessageEvent>event));
window.postMessage(message);

expect(triggeredEvent).toBe(null);
Expand Down Expand Up @@ -1481,7 +1435,7 @@ describe('Window', () => {
};
let triggeredEvent: MessageEvent | null = null;

window.addEventListener('message', (event) => (triggeredEvent = event));
window.addEventListener('message', (event) => (triggeredEvent = <MessageEvent>event));
window.postMessage(message);

expect(triggeredEvent).toBe(null);
Expand Down

0 comments on commit f7dab33

Please sign in to comment.