test
Some checks failed
test / Run tests (push) Failing after 6s
build / Build (push) Failing after 9s

This commit is contained in:
2025-03-19 18:05:54 +08:00
parent c2219db73e
commit fd2aba4286
31 changed files with 584 additions and 199 deletions

View File

@ -0,0 +1,3 @@
# mock一个chrome扩展环境
> 只针对自己的项目做了一些简单的封装,如果有需要可以自己修改

View File

@ -0,0 +1,32 @@
export default class Cookies {
getAllCookieStores(
callback: (cookieStores: chrome.cookies.CookieStore[]) => void
) {
callback([
{
id: "0",
tabIds: [1],
},
]);
}
mockGetAll?: (
details: chrome.cookies.GetAllDetails,
callback: (cookies: chrome.cookies.Cookie[]) => void
) => void | undefined;
getAll(
details: chrome.cookies.GetAllDetails,
callback: (cookies: chrome.cookies.Cookie[]) => void
): void {
this.mockGetAll?.(details, callback);
}
set(details: chrome.cookies.SetDetails, callback?: () => void): void {
callback?.();
}
remove(details: chrome.cookies.Details, callback?: () => void): void {
callback?.();
}
}

View File

@ -0,0 +1,38 @@
export default class DeclarativeNetRequest {
HeaderOperation = {
APPEND: "append",
SET: "set",
REMOVE: "remove",
};
RuleActionType = {
BLOCK: "block",
REDIRECT: "redirect",
ALLOW: "allow",
UPGRADE_SCHEME: "upgradeScheme",
MODIFY_HEADERS: "modifyHeaders",
ALLOW_ALL_REQUESTS: "allowAllRequests",
};
ResourceType = {
MAIN_FRAME: "main_frame",
SUB_FRAME: "sub_frame",
STYLESHEET: "stylesheet",
SCRIPT: "script",
IMAGE: "image",
FONT: "font",
OBJECT: "object",
XMLHTTPREQUEST: "xmlhttprequest",
PING: "ping",
CSP_REPORT: "csp_report",
MEDIA: "media",
WEBSOCKET: "websocket",
OTHER: "other",
};
updateSessionRules() {
return new Promise<void>((resolve) => {
resolve();
});
}
}

View File

@ -0,0 +1,5 @@
export default class Downloads {
download(_: any, callback: Function) {
callback && callback();
}
}

View File

@ -0,0 +1,9 @@
export default class I18n {
getUILanguage() {
return "zh-CN";
}
getAcceptLanguages(callback: (lngs: string[]) => void) {
callback(["zh-CN"]);
}
}

View File

@ -0,0 +1,26 @@
import Cookies from "./cookies";
import Downloads from "./downloads";
import Notifications from "./notifications";
import Runtime from "./runtime";
import MockTab from "./tab";
import WebRequest from "./web_reqeuest";
import Storage from "./storage";
import I18n from "./i18n";
import DeclarativeNetRequest from "./declarativ_net_request";
const chromeMock = {
tabs: new MockTab(),
runtime: new Runtime(),
webRequest: new WebRequest(),
notifications: new Notifications(),
downloads: new Downloads(),
cookies: new Cookies(),
storage: new Storage(),
i18n: new I18n(),
declarativeNetRequest: new DeclarativeNetRequest(),
init() {},
};
// @ts-ignore
global.chrome = chromeMock;
export default chromeMock;

View File

@ -0,0 +1,64 @@
export default class Notifications {
notification: Map<string, boolean> = new Map();
onClosedHandler?: (id: string, byUser: boolean) => void;
onClosed = {
addListener: (
callback: (notificationId: string, byUser: boolean) => void
) => {
this.onClosedHandler = callback;
},
};
onButtonClickedHandler?: (id: string, index: number) => void;
onButtonClicked = {
addListener: (
callback: (notificationId: string, buttonIndex: number) => void
) => {
this.onButtonClickedHandler = callback;
},
};
mockClickButton(id: string, index: number) {
this.onButtonClickedHandler?.(id, index);
}
onClickedHandler?: (id: string) => void;
onClicked = {
addListener: (callback: (notificationId: string) => void) => {
this.onClickedHandler = callback;
},
};
create(
options: chrome.notifications.NotificationOptions,
callback?: (id: string) => void
) {
const id = Math.random().toString();
this.notification.set(id, true);
if (callback) {
callback(id);
}
}
clear(id: string) {
if (!this.notification.has(id)) {
throw new Error("notification not found");
}
this.notification.delete(id);
}
update(id: string) {
if (!this.notification.has(id)) {
throw new Error("notification not found");
}
return true;
}
mockClick(id: string) {
this.onClickedHandler?.(id);
}
}

View File

@ -0,0 +1,62 @@
type Port = chrome.runtime.Port & {
setTargetPort: (port: chrome.runtime.Port) => void;
messageListener: Array<(message: any) => void>;
};
export default class Runtime {
connectListener: Array<(port: chrome.runtime.Port) => void> = [];
onConnect = {
addListener: (callback: (port: chrome.runtime.Port) => void) => {
this.connectListener.push(callback);
},
};
Port(connectInfo?: chrome.runtime.ConnectInfo) {
const messageListener: Array<(message: any) => void> = [];
let targetPort: Port;
return {
setTargetPort(port: Port) {
targetPort = port;
},
messageListener,
name: connectInfo?.name || "",
sender: {
tab: {
id: 1,
} as unknown as chrome.tabs.Tab,
url: window.location.href,
},
postMessage(message: any) {
messageListener.forEach((callback) => {
callback(message);
});
},
onMessage: {
addListener(callback: (message: any) => void) {
targetPort.messageListener.push(callback);
},
} as unknown as chrome.events.Event<(message: any) => void>,
onDisconnect: {
addListener() {
// do nothing
},
} as unknown as chrome.events.Event<() => void>,
} as unknown as Port;
}
connect(connectInfo?: chrome.runtime.ConnectInfo) {
const port = this.Port(connectInfo);
const targetPort = this.Port(connectInfo);
targetPort.setTargetPort(port);
port.setTargetPort(targetPort);
this.connectListener.forEach((callback) => {
callback(targetPort);
});
return port;
}
getURL(path: string) {
return `${window.location.href}${path}`;
}
}

View File

@ -0,0 +1,33 @@
export default class Storage {
sync = new CrhomeStorage();
local = new CrhomeStorage();
session = new CrhomeStorage();
}
export class CrhomeStorage {
data: any = {};
get(key: string, callback: (data: any) => void) {
if (key === null) {
callback(this.data);
return;
}
callback({ [key]: this.data[key] });
}
set(data: any, callback: () => void) {
this.data = Object.assign(this.data, data);
callback();
}
remove(keys: string | string[], callback: () => void) {
if (typeof keys === "string") {
delete this.data[keys];
} else {
keys.forEach((key) => {
delete this.data[key];
});
}
callback();
}
}

View File

@ -0,0 +1,28 @@
import EventEmitter from "eventemitter3";
export default class MockTab {
hook = new EventEmitter();
query() {
return new Promise((resolve) => {
resolve([]);
});
}
create(createProperties: chrome.tabs.CreateProperties, callback?: (tab: chrome.tabs.Tab) => void) {
this.hook.emit("create", createProperties);
callback?.({
id: 1,
} as chrome.tabs.Tab);
}
remove(tabId: number) {
this.hook.emit("remove", tabId);
}
onRemoved = {
addListener: (callback: any) => {
this.hook.addListener("remove", callback);
},
};
}

View File

@ -0,0 +1,59 @@
export default class WebRequest {
sendHeader?: (
details: chrome.webRequest.WebRequestHeadersDetails
) => chrome.webRequest.BlockingResponse | void;
mockXhr(xhr: any): any {
// eslint-disable-next-line no-underscore-dangle
const _this = this;
// eslint-disable-next-line func-names
return function () {
// eslint-disable-next-line new-cap
const ret = new xhr();
const header: chrome.webRequest.HttpHeader[] = [];
ret.setRequestHeader = (k: string, v: string) => {
header.push({
name: k,
value: v,
});
};
const oldSend = ret.send.bind(ret);
ret.send = (data: any) => {
header.push({
name: "cookie",
value: "website=example.com",
});
const resp = _this.sendHeader?.({
method: ret.method,
url: ret.url,
requestHeaders: header,
initiator: chrome.runtime.getURL(""),
} as chrome.webRequest.WebRequestHeadersDetails) as chrome.webRequest.BlockingResponse;
resp.requestHeaders?.forEach((h) => {
// eslint-disable-next-line no-underscore-dangle
ret._authorRequestHeaders!.addHeader(h.name, h.value);
});
oldSend(data);
};
return ret;
};
}
onBeforeSendHeaders = {
addListener: (callback: any) => {
this.sendHeader = callback;
},
};
onHeadersReceived = {
addListener: () => {
// TODO
},
};
onCompleted = {
addListener: () => {
// TODO
},
};
}

View File

@ -0,0 +1,61 @@
import EventEmitter from "eventemitter3";
import { Message, MessageConnect, MessageSend } from "./server";
export class MockMessageConnect implements MessageConnect {
constructor(protected EE: EventEmitter) {}
onMessage(callback: (data: any) => void): void {
this.EE.on("message", (data: any) => {
callback(data);
});
}
sendMessage(data: any): void {
this.EE.emit("message", data);
}
disconnect(): void {
this.EE.emit("disconnect");
}
onDisconnect(callback: () => void): void {
this.EE.on("disconnect", callback);
}
}
export class MockMessageSend implements MessageSend {
constructor(
protected EE: EventEmitter,
) {}
connect(data: any): Promise<MessageConnect> {
return new Promise((resolve) => {
const EE = new EventEmitter();
const con = new MockMessageConnect(EE);
this.EE.emit("connect", data, con);
resolve(con);
});
}
sendMessage(data: any): Promise<any> {
return new Promise((resolve) => {
this.EE.emit("message", data, (resp: any) => {
resolve(resp);
});
});
}
}
export class MockMessage extends MockMessageSend implements Message {
onConnect(callback: (data: any, con: MessageConnect) => void): void {
this.EE.on("connect", (data: any, con: MessageConnect) => {
callback(data, con);
});
}
onMessage(callback: (data: any, sendResponse: (data: any) => void) => void): void {
this.EE.on("message", (data: any, sendResponse: (data: any) => void) => {
callback(data, sendResponse);
});
}
}