323 lines
8.0 KiB
JavaScript
323 lines
8.0 KiB
JavaScript
/** @odoo-module */
|
|
|
|
import { createMock, HootError, makePublicListeners, MIME_TYPE } from "../hoot_utils";
|
|
import { getSyncValue, setSyncValue } from "./sync_values";
|
|
|
|
/**
|
|
* @typedef {"android" | "ios" | "linux" | "mac" | "windows"} Platform
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Global
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const {
|
|
Blob,
|
|
ClipboardItem = class NonSecureClipboardItem {},
|
|
EventTarget,
|
|
navigator,
|
|
Object: { assign: $assign },
|
|
Set,
|
|
TypeError,
|
|
} = globalThis;
|
|
const { userAgent: $userAgent } = navigator;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Internal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const getBlobValue = (value) => (value instanceof Blob ? value.text() : value);
|
|
|
|
/**
|
|
* Returns the final synchronous value of several item types.
|
|
*
|
|
* @param {unknown} value
|
|
* @param {string} type
|
|
*/
|
|
const getClipboardValue = (value, type) =>
|
|
getBlobValue(value instanceof ClipboardItem ? value.getType(type) : value);
|
|
|
|
const getMockValues = () => ({
|
|
/** @type {typeof Navigator["prototype"]["sendBeacon"]} */
|
|
sendBeacon: throwNotImplemented("sendBeacon"),
|
|
userAgent: makeUserAgent("linux"),
|
|
/** @type {typeof Navigator["prototype"]["vibrate"]} */
|
|
vibrate: throwNotImplemented("vibrate"),
|
|
});
|
|
|
|
/**
|
|
* @returns {Record<PermissionName, { name: string; state: PermissionState }>}
|
|
*/
|
|
const getPermissions = () => ({
|
|
"background-sync": {
|
|
state: "granted", // should always be granted
|
|
name: "background_sync",
|
|
},
|
|
"local-fonts": {
|
|
state: "denied",
|
|
name: "local_fonts",
|
|
},
|
|
"payment-handler": {
|
|
state: "denied",
|
|
name: "payment_handler",
|
|
},
|
|
"persistent-storage": {
|
|
state: "denied",
|
|
name: "durable_storage",
|
|
},
|
|
"screen-wake-lock": {
|
|
state: "denied",
|
|
name: "screen_wake_lock",
|
|
},
|
|
"storage-access": {
|
|
state: "denied",
|
|
name: "storage-access",
|
|
},
|
|
"window-management": {
|
|
state: "denied",
|
|
name: "window_placement",
|
|
},
|
|
accelerometer: {
|
|
state: "denied",
|
|
name: "sensors",
|
|
},
|
|
camera: {
|
|
state: "denied",
|
|
name: "video_capture",
|
|
},
|
|
geolocation: {
|
|
state: "denied",
|
|
name: "geolocation",
|
|
},
|
|
gyroscope: {
|
|
state: "denied",
|
|
name: "sensors",
|
|
},
|
|
magnetometer: {
|
|
state: "denied",
|
|
name: "sensors",
|
|
},
|
|
microphone: {
|
|
state: "denied",
|
|
name: "audio_capture",
|
|
},
|
|
midi: {
|
|
state: "denied",
|
|
name: "midi",
|
|
},
|
|
notifications: {
|
|
state: "denied",
|
|
name: "notifications",
|
|
},
|
|
push: {
|
|
state: "denied",
|
|
name: "push",
|
|
},
|
|
});
|
|
|
|
const getUserAgentBrowser = () => {
|
|
if (/Firefox/i.test($userAgent)) {
|
|
return "Gecko/20100101 Firefox/1000.0"; // Firefox
|
|
}
|
|
if (/Chrome/i.test($userAgent)) {
|
|
return "AppleWebKit/1000.00 (KHTML, like Gecko) Chrome/1000.00 Safari/1000.00"; // Chrome
|
|
}
|
|
if (/Safari/i.test($userAgent)) {
|
|
return "AppleWebKit/1000.00 (KHTML, like Gecko) Version/1000.00 Safari/1000.00"; // Safari
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {Platform} platform
|
|
*/
|
|
const makeUserAgent = (platform) => {
|
|
const userAgent = ["Mozilla/5.0"];
|
|
switch (platform.toLowerCase()) {
|
|
case "android": {
|
|
userAgent.push("(Linux; Android 1000)");
|
|
break;
|
|
}
|
|
case "ios": {
|
|
userAgent.push("(iPhone; CPU iPhone OS 1000_0 like Mac OS X)");
|
|
break;
|
|
}
|
|
case "linux": {
|
|
userAgent.push("(X11; Linux x86_64)");
|
|
break;
|
|
}
|
|
case "mac":
|
|
case "macintosh": {
|
|
userAgent.push("(Macintosh; Intel Mac OS X 10_15_7)");
|
|
break;
|
|
}
|
|
case "win":
|
|
case "windows": {
|
|
userAgent.push("(Windows NT 10.0; Win64; x64)");
|
|
break;
|
|
}
|
|
default: {
|
|
userAgent.push(platform);
|
|
}
|
|
}
|
|
if (userAgentBrowser) {
|
|
userAgent.push(userAgentBrowser);
|
|
}
|
|
return userAgent.join(" ");
|
|
};
|
|
|
|
/**
|
|
* @param {string} fnName
|
|
*/
|
|
const throwNotImplemented = (fnName) => {
|
|
return function notImplemented() {
|
|
throw new HootError(`Unmocked navigator method: ${fnName}`);
|
|
};
|
|
};
|
|
|
|
/** @type {Set<MockPermissionStatus>} */
|
|
const permissionStatuses = new Set();
|
|
const userAgentBrowser = getUserAgentBrowser();
|
|
const mockValues = getMockValues();
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Exports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
export class MockClipboard {
|
|
/** @type {unknown} */
|
|
_value = null;
|
|
|
|
async read() {
|
|
return this._value;
|
|
}
|
|
|
|
async readText() {
|
|
return String(getClipboardValue(this._value, MIME_TYPE.text) ?? "");
|
|
}
|
|
|
|
async write(value) {
|
|
this._value = value;
|
|
}
|
|
|
|
async writeText(value) {
|
|
this._value = String(getClipboardValue(value, MIME_TYPE.text) ?? "");
|
|
}
|
|
}
|
|
|
|
export class MockClipboardItem extends ClipboardItem {
|
|
constructor(items) {
|
|
super(items);
|
|
|
|
setSyncValue(this, items);
|
|
}
|
|
|
|
// Added synchronous methods to enhance speed in tests
|
|
|
|
async getType(type) {
|
|
return getSyncValue(this)[type];
|
|
}
|
|
}
|
|
|
|
export class MockPermissions {
|
|
/**
|
|
* @param {PermissionDescriptor} permissionDesc
|
|
*/
|
|
async query({ name }) {
|
|
if (!(name in currentPermissions)) {
|
|
throw new TypeError(
|
|
`The provided value '${name}' is not a valid enum value of type PermissionName`
|
|
);
|
|
}
|
|
return new MockPermissionStatus(name);
|
|
}
|
|
}
|
|
|
|
export class MockPermissionStatus extends EventTarget {
|
|
/** @type {typeof currentPermissions[PermissionName]} */
|
|
_permission;
|
|
|
|
/**
|
|
* @param {PermissionName} name
|
|
* @param {PermissionState} value
|
|
*/
|
|
constructor(name) {
|
|
super(...arguments);
|
|
|
|
makePublicListeners(this, ["change"]);
|
|
|
|
this._permission = currentPermissions[name];
|
|
permissionStatuses.add(this);
|
|
}
|
|
|
|
get name() {
|
|
return this._permission.name;
|
|
}
|
|
|
|
get state() {
|
|
return this._permission.state;
|
|
}
|
|
}
|
|
|
|
export const currentPermissions = getPermissions();
|
|
|
|
export const mockClipboard = new MockClipboard();
|
|
|
|
export const mockPermissions = new MockPermissions();
|
|
|
|
export const mockNavigator = createMock(navigator, {
|
|
clipboard: { value: mockClipboard },
|
|
maxTouchPoints: { get: () => (globalThis.ontouchstart === undefined ? 0 : 1) },
|
|
permissions: { value: mockPermissions },
|
|
sendBeacon: { get: () => mockValues.sendBeacon },
|
|
serviceWorker: { get: () => undefined },
|
|
userAgent: { get: () => mockValues.userAgent },
|
|
vibrate: { get: () => mockValues.vibrate },
|
|
});
|
|
|
|
export function cleanupNavigator() {
|
|
permissionStatuses.clear();
|
|
$assign(currentPermissions, getPermissions());
|
|
$assign(mockValues, getMockValues());
|
|
}
|
|
|
|
/**
|
|
* @param {PermissionName} name
|
|
* @param {PermissionState} [value]
|
|
*/
|
|
export function mockPermission(name, value) {
|
|
if (!(name in currentPermissions)) {
|
|
throw new TypeError(
|
|
`The provided value '${name}' is not a valid enum value of type PermissionName`
|
|
);
|
|
}
|
|
|
|
currentPermissions[name].state = value;
|
|
|
|
for (const permissionStatus of permissionStatuses) {
|
|
if (permissionStatus.name === name) {
|
|
permissionStatus.dispatchEvent(new Event("change"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {typeof Navigator["prototype"]["sendBeacon"]} callback
|
|
*/
|
|
export function mockSendBeacon(callback) {
|
|
mockValues.sendBeacon = callback;
|
|
}
|
|
|
|
/**
|
|
* @param {Platform} platform
|
|
*/
|
|
export function mockUserAgent(platform = "linux") {
|
|
mockValues.userAgent = makeUserAgent(platform);
|
|
}
|
|
|
|
/**
|
|
* @param {typeof Navigator["prototype"]["vibrate"]} callback
|
|
*/
|
|
export function mockVibrate(callback) {
|
|
mockValues.vibrate = callback;
|
|
}
|