test/lifecycle.test.js (3633 bytes)
1 "use strict"; 2 const test = require("node:test"); 3 const assert = require("node:assert/strict"); 4 const {loadBdPatcher} = require("./helpers/installed.js"); 5 const PluginProfiler = require("../00PluginProfiler.plugin.js"); 6 7 const META = {filename: "00PluginProfiler.plugin.js", name: "PluginProfiler"}; 8 9 function fakeWindow() { 10 return { 11 setTimeout: () => 1, 12 setInterval: () => 2, 13 MutationObserver: class {}, 14 ResizeObserver: class {}, 15 IntersectionObserver: class {} 16 }; 17 } 18 19 test.afterEach(() => { 20 delete globalThis.window; 21 delete globalThis.BdApi; 22 }); 23 24 test("long-frame labels show script files and plugin names, but never the page URL", () => { 25 const plugin = new PluginProfiler(META); 26 plugin.fileOwners = new Map([["Foo.plugin.js", "Foo"]]); 27 const page = "https://discord.com/channels/@me/123456789012345678"; 28 assert.equal(plugin.scriptLabel({sourceURL: page, invoker: page, invokerType: "classic-script"}), "page"); 29 assert.equal(plugin.scriptLabel({sourceURL: page, invoker: "DIV.onclick"}), "page (DIV.onclick)"); 30 assert.equal(plugin.scriptLabel({sourceURL: "https://discord.com/assets/8fe5.js", invoker: "https://discord.com/assets/8fe5.js"}), "8fe5.js"); 31 assert.equal(plugin.scriptLabel({sourceURL: "https://discord.com/assets/5567.js?v=1", sourceFunctionName: "r"}), "5567.js (r)"); 32 assert.equal(plugin.scriptLabel({sourceURL: "betterdiscord://betterdiscord/plugins/Foo.plugin.js", invoker: "Window.setTimeout"}), "Foo (Window.setTimeout)"); 33 }); 34 35 test("a failed start removes every hook", () => { 36 const scope = globalThis.window = fakeWindow(); 37 const originals = {...scope}; 38 globalThis.BdApi = { 39 Plugins: {getAll() { throw new Error("broken API"); }}, 40 Patcher: {getPatchesByCaller: () => []} 41 }; 42 const plugin = new PluginProfiler(META); 43 assert.throws(() => plugin.start(), /broken API/); 44 for (const key of Object.keys(originals)) assert.equal(scope[key], originals[key], key); 45 assert.equal(plugin.recorder, null); 46 }); 47 48 test("start wraps patches and hooks of other plugins, and stop removes all of them", () => { 49 const Patcher = loadBdPatcher({err() {}}); 50 const target = {work() { return 1; }}; 51 const callback = () => 2; 52 Patcher.after("Foo", target, "work", callback); 53 const observer = () => {}; 54 const instance = {observer, start() {}, stop() {}}; 55 const scope = globalThis.window = fakeWindow(); 56 const originals = {...scope}; 57 globalThis.BdApi = { 58 Plugins: {getAll: () => [{name: "Foo", id: "Foo", filename: "Foo.plugin.js", instance}, {...META, id: META.name, instance: {}}]}, 59 Patcher: {getPatchesByCaller: caller => Patcher.getPatchesByCaller(caller)} 60 }; 61 62 const plugin = new PluginProfiler(META); 63 plugin.start(); 64 try { 65 assert.notEqual(Patcher.getPatchesByCaller("Foo")[0].callback, callback); 66 assert.notEqual(instance.observer, observer); 67 assert.notEqual(scope.setTimeout, originals.setTimeout); 68 assert.equal(target.work(), 2); 69 instance.observer(); 70 const row = plugin.snapshot().rows.find(r => r.name === "Foo"); 71 assert.equal(row.calls, 2); 72 assert.match(plugin.report(), /^PluginProfiler 1\.0\.0 report/); 73 assert.match(plugin.report(), /\nFoo /); 74 } 75 finally { 76 plugin.stop(); 77 } 78 assert.equal(Patcher.getPatchesByCaller("Foo")[0].callback, callback); 79 assert.equal(instance.observer, observer); 80 for (const key of Object.keys(originals)) assert.equal(scope[key], originals[key], key); 81 assert.equal(plugin.snapshot(), null); 82 });