867 lines
22 KiB
JavaScript
867 lines
22 KiB
JavaScript
/** @odoo-module */
|
|
|
|
import {
|
|
mockedCancelAnimationFrame,
|
|
mockedRequestAnimationFrame,
|
|
} from "@web/../lib/hoot-dom/helpers/time";
|
|
import { makeNetworkLogger } from "../core/logger";
|
|
import { ensureArray, makePublicListeners, MIME_TYPE } from "../hoot_utils";
|
|
import { getSyncValue, MockBlob, setSyncValue } from "./sync_values";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Global
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const {
|
|
AbortController,
|
|
BroadcastChannel,
|
|
document,
|
|
EventTarget,
|
|
fetch,
|
|
Headers,
|
|
Map,
|
|
Math: { max: $max, min: $min },
|
|
Object: { assign: $assign, entries: $entries, fromEntries: $fromEntries },
|
|
ProgressEvent,
|
|
Request,
|
|
Response,
|
|
Set,
|
|
SharedWorker,
|
|
URL,
|
|
WebSocket,
|
|
Worker,
|
|
} = globalThis;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Internal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @param {SharedWorker | Worker} worker
|
|
*/
|
|
const makeWorkerScope = (worker) => {
|
|
const execute = async () => {
|
|
const scope = new MockDedicatedWorkerGlobalScope(worker);
|
|
const keys = Reflect.ownKeys(scope);
|
|
const values = $fromEntries(keys.map((key) => [key, globalThis[key]]));
|
|
$assign(globalThis, scope);
|
|
|
|
script(scope);
|
|
mockWorkerConnection(worker);
|
|
|
|
if (typeof globalThis.onconnect === "function") {
|
|
globalThis.onconnect();
|
|
}
|
|
|
|
$assign(globalThis, values);
|
|
};
|
|
|
|
const load = async () => {
|
|
await Promise.resolve();
|
|
|
|
const response = await globalThis.fetch(worker.url);
|
|
const content = await response.text();
|
|
script = new Function("self", content);
|
|
};
|
|
|
|
let script = () => {};
|
|
|
|
return { execute, load };
|
|
};
|
|
|
|
const DEFAULT_URL = "https://www.hoot.test/";
|
|
const HEADER = {
|
|
contentType: "Content-Type",
|
|
};
|
|
const R_INTERNAL_URL = /^(blob|file):/;
|
|
|
|
/** @type {Set<WebSocket>} */
|
|
const openClientWebsockets = new Set();
|
|
/** @type {Set<AbortController>} */
|
|
const openRequestControllers = new Set();
|
|
/** @type {Set<ServerWebSocket>} */
|
|
const openServerWebsockets = new Set();
|
|
/** @type {Map<SharedWorker | Worker, Promise<any>>} */
|
|
const openWorkers = new Map();
|
|
|
|
/** @type {(typeof fetch) | null} */
|
|
let mockFetchFn = null;
|
|
/** @type {((worker: Worker | MessagePort) => any) | null} */
|
|
let mockWorkerConnection = null;
|
|
/** @type {((websocket: ServerWebSocket) => any) | null} */
|
|
let mockWebSocketConnection = null;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Exports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
export function cleanupNetwork() {
|
|
// Requests
|
|
for (const controller of openRequestControllers) {
|
|
controller.abort();
|
|
}
|
|
openRequestControllers.clear();
|
|
mockFetchFn = null;
|
|
|
|
// Websockets
|
|
for (const ws of openServerWebsockets) {
|
|
ws.close();
|
|
}
|
|
openServerWebsockets.clear();
|
|
mockWebSocketConnection = null;
|
|
|
|
// Workers
|
|
for (const [worker] of openWorkers) {
|
|
if ("port" in worker) {
|
|
worker.port.close();
|
|
} else {
|
|
worker.terminate();
|
|
}
|
|
}
|
|
openWorkers.clear();
|
|
mockWorkerConnection = null;
|
|
|
|
// Other APIs
|
|
mockCookie._clear();
|
|
mockHistory._clear();
|
|
mockLocation._clear();
|
|
MockBroadcastChannel._clear();
|
|
}
|
|
|
|
/** @type {typeof fetch} */
|
|
export async function mockedFetch(input, init) {
|
|
if (R_INTERNAL_URL.test(input)) {
|
|
// Internal URL: directly handled by the browser
|
|
return fetch(input, init);
|
|
}
|
|
if (!mockFetchFn) {
|
|
throw new Error("Can't make a request when fetch is not mocked");
|
|
}
|
|
init ||= {};
|
|
const method = init.method?.toUpperCase() || (init.body ? "POST" : "GET");
|
|
const { logRequest, logResponse } = makeNetworkLogger(method, input);
|
|
|
|
const controller = new AbortController();
|
|
init.signal = controller.signal;
|
|
|
|
logRequest(() => (typeof init.body === "string" ? JSON.parse(init.body) : init));
|
|
|
|
openRequestControllers.add(controller);
|
|
let failed = false;
|
|
let result;
|
|
try {
|
|
result = await mockFetchFn(input, init);
|
|
} catch (err) {
|
|
result = err;
|
|
failed = true;
|
|
}
|
|
if (!openRequestControllers.has(controller)) {
|
|
return new Promise(() => {});
|
|
}
|
|
openRequestControllers.delete(controller);
|
|
if (failed) {
|
|
throw result;
|
|
}
|
|
|
|
/** @type {Headers} */
|
|
let headers;
|
|
if (result && result.headers instanceof Headers) {
|
|
headers = result.headers;
|
|
} else if (init.headers instanceof Headers) {
|
|
headers = init.headers;
|
|
} else {
|
|
headers = new Headers(init.headers);
|
|
}
|
|
|
|
let contentType = headers.get(HEADER.contentType);
|
|
|
|
if (result instanceof MockResponse) {
|
|
// Mocked response
|
|
logResponse(async () => {
|
|
const textValue = getSyncValue(result);
|
|
return contentType === MIME_TYPE.json ? JSON.parse(textValue) : textValue;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (result instanceof Response) {
|
|
// Actual fetch
|
|
logResponse(() => "(go to network tab for request content)");
|
|
return result;
|
|
}
|
|
|
|
// Not a response object:
|
|
// Determine the return type based on:
|
|
// - the content type header
|
|
// - or the type of the returned value
|
|
if (!contentType) {
|
|
if (typeof result === "string") {
|
|
contentType = MIME_TYPE.text;
|
|
} else if (result instanceof Blob) {
|
|
contentType = MIME_TYPE.blob;
|
|
} else {
|
|
contentType = MIME_TYPE.json;
|
|
}
|
|
}
|
|
|
|
if (contentType === MIME_TYPE.json) {
|
|
// JSON response
|
|
const strBody = JSON.stringify(result ?? null);
|
|
logResponse(() => result);
|
|
return new MockResponse(strBody, { [HEADER.contentType]: contentType });
|
|
}
|
|
|
|
// Any other type (blob / text)
|
|
logResponse(() => result);
|
|
return new MockResponse(result, { [HEADER.contentType]: contentType });
|
|
}
|
|
|
|
/**
|
|
* Mocks the fetch function by replacing it with a given `fetchFn`.
|
|
*
|
|
* The return value of `fetchFn` is used as the response of the mocked fetch, or
|
|
* wrapped in a {@link MockResponse} object if it does not meet the required format.
|
|
*
|
|
* @param {typeof mockFetchFn} [fetchFn]
|
|
* @example
|
|
* mockFetch((input, init) => {
|
|
* if (input === "/../web_search_read") {
|
|
* return { records: [{ id: 3, name: "john" }] };
|
|
* }
|
|
* // ...
|
|
* });
|
|
* @example
|
|
* mockFetch((input, init) => {
|
|
* if (input === "/translations") {
|
|
* const translations = {
|
|
* "Hello, world!": "Bonjour, monde !",
|
|
* // ...
|
|
* };
|
|
* return new Response(JSON.stringify(translations));
|
|
* }
|
|
* });
|
|
*/
|
|
export function mockFetch(fetchFn) {
|
|
mockFetchFn = fetchFn;
|
|
}
|
|
|
|
/**
|
|
* Activates mock WebSocket classe:
|
|
* - websocket connections will be handled by `window.fetch` (see {@link mockFetch});
|
|
* - the `onWebSocketConnected` callback will be called after a websocket has been created.
|
|
*
|
|
* @param {typeof mockWebSocketConnection} [onWebSocketConnected]
|
|
*/
|
|
export function mockWebSocket(onWebSocketConnected) {
|
|
mockWebSocketConnection = onWebSocketConnected;
|
|
}
|
|
|
|
/**
|
|
* Activates mock Worker and SharedWorker classes:
|
|
* - actual code fetched by worker URLs will then be handled by `window.fetch`
|
|
* (see {@link mockFetch});
|
|
* - the `onWorkerConnected` callback will be called after a worker has been created.
|
|
*
|
|
* @param {typeof mockWorkerConnection} [onWorkerConnected]
|
|
* @example
|
|
* mockWorker((worker) => {
|
|
* worker.addEventListener("message", (event) => {
|
|
* expect.step(event.type);
|
|
* });
|
|
* });
|
|
*/
|
|
export function mockWorker(onWorkerConnected) {
|
|
mockWorkerConnection = onWorkerConnected;
|
|
}
|
|
|
|
export class MockBroadcastChannel extends BroadcastChannel {
|
|
static _instances = [];
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
MockBroadcastChannel._instances.push(this);
|
|
}
|
|
|
|
static _clear() {
|
|
while (MockBroadcastChannel._instances.length) {
|
|
MockBroadcastChannel._instances.pop().close();
|
|
}
|
|
}
|
|
}
|
|
|
|
export class MockCookie {
|
|
/** @type {Record<string, string>} */
|
|
_jar = {};
|
|
|
|
get() {
|
|
return $entries(this._jar)
|
|
.filter(([, value]) => value !== "kill")
|
|
.map((entry) => entry.join("="))
|
|
.join("; ");
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
*/
|
|
set(value) {
|
|
for (const cookie of String(value).split(/\s*;\s*/)) {
|
|
const [key, value] = cookie.split(/=(.*)/);
|
|
if (!["path", "max-age"].includes(key)) {
|
|
this._jar[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
_clear() {
|
|
this._jar = {};
|
|
}
|
|
}
|
|
|
|
export class MockDedicatedWorkerGlobalScope {
|
|
/**
|
|
* @param {SharedWorker | Worker} worker
|
|
*/
|
|
constructor(worker) {
|
|
$assign(
|
|
this,
|
|
{
|
|
cancelanimationframe: mockedCancelAnimationFrame,
|
|
onconnect: null,
|
|
requestanimationframe: mockedRequestAnimationFrame,
|
|
self: this,
|
|
},
|
|
worker
|
|
);
|
|
if (!("close" in this)) {
|
|
this.close = worker.terminate.bind(worker);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class MockHistory {
|
|
_index = 0;
|
|
/** @type {Location} */
|
|
_loc;
|
|
/** @type {[any, string][]} */
|
|
_stack = [];
|
|
|
|
/** @type {typeof History.prototype.length} */
|
|
get length() {
|
|
return this._stack.length;
|
|
}
|
|
|
|
/** @type {typeof History.prototype.state} */
|
|
get state() {
|
|
const entry = this._stack[this._index];
|
|
return entry && entry[0];
|
|
}
|
|
|
|
/** @type {typeof History.prototype.scrollRestoration} */
|
|
get scrollRestoration() {
|
|
return "auto";
|
|
}
|
|
|
|
/**
|
|
* @param {Location} location
|
|
*/
|
|
constructor(location) {
|
|
this._loc = location;
|
|
this.pushState(null, "", this._loc.href);
|
|
}
|
|
|
|
/** @type {typeof History.prototype.back} */
|
|
back() {
|
|
this._index = $max(0, this._index - 1);
|
|
this._loc.assign(this._stack[this._index][1]);
|
|
this._dispatchPopState();
|
|
}
|
|
|
|
/** @type {typeof History.prototype.forward} */
|
|
forward() {
|
|
this._index = $min(this._stack.length - 1, this._index + 1);
|
|
this._loc.assign(this._stack[this._index][1]);
|
|
this._dispatchPopState();
|
|
}
|
|
|
|
/** @type {typeof History.prototype.go} */
|
|
go(delta) {
|
|
this._index = $max(0, $min(this._stack.length - 1, this._index + delta));
|
|
this._loc.assign(this._stack[this._index][1]);
|
|
this._dispatchPopState();
|
|
}
|
|
|
|
/** @type {typeof History.prototype.pushState} */
|
|
pushState(data, unused, url) {
|
|
this._stack = this._stack.slice(0, this._index + 1);
|
|
this._index = this._stack.push([data ?? null, url]) - 1;
|
|
this._loc.assign(url);
|
|
}
|
|
|
|
/** @type {typeof History.prototype.replaceState} */
|
|
replaceState(data, unused, url) {
|
|
this._stack[this._index] = [data ?? null, url];
|
|
this._loc.assign(url);
|
|
}
|
|
|
|
_dispatchPopState() {
|
|
window.dispatchEvent(new PopStateEvent("popstate", { state: this.state }));
|
|
}
|
|
|
|
_clear() {
|
|
this._index = 0;
|
|
this._stack = [];
|
|
this.pushState(null, "", this._loc.href);
|
|
}
|
|
}
|
|
|
|
export class MockLocation extends EventTarget {
|
|
_anchor = document.createElement("a");
|
|
/** @type {(() => any)[]} */
|
|
_onReload = [];
|
|
|
|
get ancestorOrigins() {
|
|
return [];
|
|
}
|
|
|
|
get hash() {
|
|
return this._anchor.hash;
|
|
}
|
|
set hash(value) {
|
|
this._anchor.hash = value;
|
|
}
|
|
|
|
get host() {
|
|
return this._anchor.host;
|
|
}
|
|
set host(value) {
|
|
this._anchor.host = value;
|
|
}
|
|
|
|
get hostname() {
|
|
return this._anchor.hostname;
|
|
}
|
|
set hostname(value) {
|
|
this._anchor.hostname = value;
|
|
}
|
|
|
|
get href() {
|
|
return this._anchor.href;
|
|
}
|
|
set href(value) {
|
|
this._anchor.href = value;
|
|
}
|
|
|
|
get origin() {
|
|
return this._anchor.origin;
|
|
}
|
|
set origin(value) {
|
|
this._anchor.origin = value;
|
|
}
|
|
|
|
get pathname() {
|
|
return this._anchor.pathname;
|
|
}
|
|
set pathname(value) {
|
|
this._anchor.pathname = value;
|
|
}
|
|
|
|
get port() {
|
|
return this._anchor.port;
|
|
}
|
|
set port(value) {
|
|
this._anchor.port = value;
|
|
}
|
|
|
|
get protocol() {
|
|
return this._anchor.protocol;
|
|
}
|
|
set protocol(value) {
|
|
this._anchor.protocol = value;
|
|
}
|
|
|
|
get search() {
|
|
return this._anchor.search;
|
|
}
|
|
set search(value) {
|
|
this._anchor.search = value;
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.href = DEFAULT_URL;
|
|
|
|
makePublicListeners(this, ["reload"]);
|
|
}
|
|
|
|
assign(url) {
|
|
this.href = url;
|
|
}
|
|
|
|
reload() {
|
|
this.dispatchEvent(new CustomEvent("reload"));
|
|
}
|
|
|
|
replace(url) {
|
|
this.href = url;
|
|
}
|
|
|
|
toString() {
|
|
return this._anchor.toString();
|
|
}
|
|
|
|
_clear() {
|
|
this.href = DEFAULT_URL;
|
|
}
|
|
}
|
|
|
|
export class MockMessagePort extends EventTarget {
|
|
/** @type {() => any} */
|
|
_execute;
|
|
/** @type {SharedWorker} */
|
|
_worker;
|
|
|
|
/**
|
|
* @param {SharedWorker} worker
|
|
* @param {() => any} execute
|
|
*/
|
|
constructor(worker, execute) {
|
|
super();
|
|
|
|
this._worker = worker;
|
|
this._execute = execute;
|
|
makePublicListeners(this, ["error", "message"]);
|
|
}
|
|
|
|
/** @type {typeof MessagePort["prototype"]["close"]} */
|
|
close() {
|
|
openWorkers.delete(this._worker);
|
|
}
|
|
|
|
/** @type {typeof MessagePort["prototype"]["postMessage"]} */
|
|
postMessage(message) {
|
|
openWorkers.get(this._worker).then(() => {
|
|
if (!openWorkers.has(this._worker)) {
|
|
return;
|
|
}
|
|
this.dispatchEvent(new MessageEvent("message", { data: message }));
|
|
});
|
|
}
|
|
|
|
/** @type {typeof MessagePort["prototype"]["start"]} */
|
|
start() {
|
|
openWorkers.get(this._worker).then(() => {
|
|
if (!openWorkers.has(this._worker)) {
|
|
return;
|
|
}
|
|
this._execute();
|
|
});
|
|
}
|
|
}
|
|
|
|
export class MockRequest extends Request {
|
|
/**
|
|
* @param {RequestInfo} input
|
|
* @param {RequestInit} [init]
|
|
*/
|
|
constructor(input, init) {
|
|
super(input, init);
|
|
|
|
setSyncValue(this, init?.body ?? null);
|
|
}
|
|
|
|
async arrayBuffer() {
|
|
return new TextEncoder().encode(getSyncValue(this));
|
|
}
|
|
|
|
async blob() {
|
|
return new MockBlob([getSyncValue(this)]);
|
|
}
|
|
|
|
async json() {
|
|
return JSON.parse(getSyncValue(this));
|
|
}
|
|
|
|
async text() {
|
|
return getSyncValue(this);
|
|
}
|
|
}
|
|
|
|
export class MockResponse extends Response {
|
|
/**
|
|
* @param {BodyInit} body
|
|
* @param {ResponseInit} [init]
|
|
*/
|
|
constructor(body, init) {
|
|
super(body, init);
|
|
|
|
setSyncValue(this, body ?? null);
|
|
}
|
|
|
|
async arrayBuffer() {
|
|
return new TextEncoder().encode(getSyncValue(this)).buffer;
|
|
}
|
|
|
|
async blob() {
|
|
return new MockBlob([getSyncValue(this)]);
|
|
}
|
|
|
|
async json() {
|
|
return JSON.parse(getSyncValue(this));
|
|
}
|
|
|
|
async text() {
|
|
return getSyncValue(this);
|
|
}
|
|
}
|
|
|
|
export class MockSharedWorker extends EventTarget {
|
|
/**
|
|
* @param {string | URL} scriptURL
|
|
* @param {WorkerOptions} [options]
|
|
*/
|
|
constructor(scriptURL, options) {
|
|
if (!mockWorkerConnection) {
|
|
const worker = new SharedWorker(...arguments);
|
|
openWorkers.set(worker, Promise.resolve());
|
|
return worker;
|
|
}
|
|
|
|
super();
|
|
|
|
const { execute, load } = makeWorkerScope(this);
|
|
|
|
this.url = String(scriptURL);
|
|
this.name = options?.name || "";
|
|
this.port = new MockMessagePort(this, execute);
|
|
makePublicListeners(this, ["error"]);
|
|
|
|
openWorkers.set(this, load());
|
|
}
|
|
}
|
|
|
|
export class MockURL extends URL {
|
|
constructor(url, base) {
|
|
super(url, base || mockLocation);
|
|
}
|
|
}
|
|
|
|
export class MockWebSocket extends EventTarget {
|
|
/** @type {ServerWebSocket | null} */
|
|
_serverWs = null;
|
|
/** @type {ReturnType<typeof makeNetworkLogger>} */
|
|
_logger = null;
|
|
_readyState = WebSocket.CONNECTING;
|
|
|
|
get readyState() {
|
|
return this._readyState;
|
|
}
|
|
|
|
/**
|
|
* @param {string | URL} url
|
|
* @param {string | string[]} [protocols]
|
|
*/
|
|
constructor(url, protocols) {
|
|
if (!mockWebSocketConnection) {
|
|
return new WebSocket(url, protocols);
|
|
}
|
|
|
|
super();
|
|
openClientWebsockets.add(this);
|
|
|
|
this.url = String(url);
|
|
this.protocols = ensureArray(protocols || "");
|
|
this._logger = makeNetworkLogger("WS", this.url);
|
|
this._serverWs = new ServerWebSocket(this, this._logger);
|
|
makePublicListeners(this, ["close", "error", "message", "open"]);
|
|
|
|
this.addEventListener("close", () => openClientWebsockets.delete(this));
|
|
this._readyState = WebSocket.OPEN;
|
|
}
|
|
|
|
/** @type {typeof WebSocket["prototype"]["close"]} */
|
|
close(code, reason) {
|
|
if (this.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
this._readyState = WebSocket.CLOSING;
|
|
this._serverWs.dispatchEvent(new CloseEvent("close", { code, reason }));
|
|
this._readyState = WebSocket.CLOSED;
|
|
openClientWebsockets.delete(this);
|
|
}
|
|
|
|
/** @type {typeof WebSocket["prototype"]["send"]} */
|
|
send(data) {
|
|
if (this.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
this._logger.logRequest(() => data);
|
|
this._serverWs.dispatchEvent(new MessageEvent("message", { data }));
|
|
}
|
|
}
|
|
|
|
export class MockWorker extends EventTarget {
|
|
/**
|
|
* @param {string | URL} scriptURL
|
|
* @param {WorkerOptions} [options]
|
|
*/
|
|
constructor(scriptURL, options) {
|
|
if (!mockWorkerConnection) {
|
|
const worker = new Worker(...arguments);
|
|
openWorkers.set(worker, Promise.resolve());
|
|
return worker;
|
|
}
|
|
|
|
super();
|
|
|
|
const { execute, load } = makeWorkerScope(this);
|
|
|
|
this.url = String(scriptURL);
|
|
this.name = options?.name || "";
|
|
makePublicListeners(this, ["error", "message"]);
|
|
|
|
openWorkers.set(this, load().then(execute));
|
|
}
|
|
|
|
/** @type {typeof Worker["prototype"]["postMessage"]} */
|
|
postMessage(message) {
|
|
openWorkers.get(this).then(() => {
|
|
if (!openWorkers.has(this)) {
|
|
return;
|
|
}
|
|
this.dispatchEvent(new MessageEvent("message", { data: message }));
|
|
});
|
|
}
|
|
|
|
/** @type {typeof Worker["prototype"]["terminate"]} */
|
|
terminate() {
|
|
openWorkers.delete(this);
|
|
}
|
|
}
|
|
|
|
export class MockXMLHttpRequest extends EventTarget {
|
|
_headers = {};
|
|
_method = "GET";
|
|
_response = null;
|
|
_status = 0;
|
|
_url = "";
|
|
|
|
abort() {}
|
|
|
|
upload = new MockXMLHttpRequestUpload();
|
|
|
|
get response() {
|
|
return this._response;
|
|
}
|
|
|
|
get status() {
|
|
return this._status;
|
|
}
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
makePublicListeners(this, ["error", "load"]);
|
|
}
|
|
|
|
/** @type {typeof XMLHttpRequest["prototype"]["open"]} */
|
|
open(method, url) {
|
|
this._method = method;
|
|
this._url = url;
|
|
}
|
|
|
|
/** @type {typeof XMLHttpRequest["prototype"]["send"]} */
|
|
async send(body) {
|
|
try {
|
|
const response = await window.fetch(this._url, {
|
|
method: this._method,
|
|
body,
|
|
headers: this._headers,
|
|
});
|
|
this._status = response.status;
|
|
this._response = await response.text();
|
|
this.dispatchEvent(new ProgressEvent("load"));
|
|
} catch (error) {
|
|
this.dispatchEvent(new ProgressEvent("error", { error }));
|
|
}
|
|
}
|
|
|
|
/** @type {typeof XMLHttpRequest["prototype"]["setRequestHeader"]} */
|
|
setRequestHeader(name, value) {
|
|
this._headers[name] = value;
|
|
}
|
|
getResponseHeader() {}
|
|
}
|
|
|
|
export class MockXMLHttpRequestUpload extends EventTarget {
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
makePublicListeners(this, [
|
|
"abort",
|
|
"error",
|
|
"load",
|
|
"loadend",
|
|
"loadstart",
|
|
"progress",
|
|
"timeout",
|
|
]);
|
|
}
|
|
}
|
|
|
|
export class ServerWebSocket extends EventTarget {
|
|
/** @type {WebSocket | null} */
|
|
_clientWs = null;
|
|
/** @type {ReturnType<typeof makeNetworkLogger>} */
|
|
_logger = null;
|
|
_readyState = WebSocket.CONNECTING;
|
|
|
|
get readyState() {
|
|
return this._readyState;
|
|
}
|
|
|
|
/**
|
|
* @param {WebSocket} websocket
|
|
* @param {ReturnType<typeof makeNetworkLogger>} logger
|
|
*/
|
|
constructor(websocket, logger) {
|
|
super(...arguments);
|
|
openServerWebsockets.add(this);
|
|
|
|
this._clientWs = websocket;
|
|
this._logger = logger;
|
|
this.url = this._clientWs.url;
|
|
|
|
mockWebSocketConnection(this);
|
|
|
|
this._logger.logRequest(() => "connection open");
|
|
|
|
this.addEventListener("close", () => openServerWebsockets.delete(this));
|
|
this._readyState = WebSocket.OPEN;
|
|
}
|
|
|
|
/** @type {typeof WebSocket["prototype"]["close"]} */
|
|
close(code, reason) {
|
|
if (this.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
this._readyState = WebSocket.CLOSING;
|
|
this._clientWs.dispatchEvent(new CloseEvent("close", { code, reason }));
|
|
this._readyState = WebSocket.CLOSED;
|
|
openServerWebsockets.delete(this);
|
|
}
|
|
|
|
/** @type {typeof WebSocket["prototype"]["send"]} */
|
|
send(data) {
|
|
if (this.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
this._logger.logResponse(() => data);
|
|
this._clientWs.dispatchEvent(new MessageEvent("message", { data }));
|
|
}
|
|
}
|
|
|
|
export const mockCookie = new MockCookie();
|
|
export const mockLocation = new MockLocation();
|
|
export const mockHistory = new MockHistory(mockLocation);
|