389 lines
13 KiB
JavaScript
389 lines
13 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const vm = require("node:vm");
|
|
|
|
const source = fs.readFileSync("Chaturbate/chaturbate-thumbnails-2x.user.js", "utf8");
|
|
let capturedCss = "";
|
|
let gmAddStyleCalls = 0;
|
|
let menuCommandName = "";
|
|
let appendedStyle = null;
|
|
let observedTarget = null;
|
|
let domContentLoadedHandler = null;
|
|
const fakeElement = {
|
|
id: "",
|
|
textContent: "",
|
|
setAttribute() {},
|
|
};
|
|
const fakeDocument = {
|
|
documentElement: {
|
|
setAttribute() {},
|
|
},
|
|
head: {
|
|
appendChild(node) {
|
|
appendedStyle = node;
|
|
},
|
|
},
|
|
createElement() {
|
|
return fakeElement;
|
|
},
|
|
getElementById() {
|
|
return null;
|
|
},
|
|
addEventListener(type, handler) {
|
|
if (type === "DOMContentLoaded") {
|
|
domContentLoadedHandler = handler;
|
|
}
|
|
},
|
|
};
|
|
|
|
vm.runInNewContext(source, {
|
|
console,
|
|
document: fakeDocument,
|
|
GM_addStyle(css) {
|
|
gmAddStyleCalls += 1;
|
|
capturedCss = css;
|
|
return fakeElement;
|
|
},
|
|
GM_registerMenuCommand(name) {
|
|
menuCommandName = name;
|
|
},
|
|
MutationObserver: class {
|
|
observe(target) {
|
|
observedTarget = target;
|
|
}
|
|
},
|
|
});
|
|
|
|
if (!capturedCss && appendedStyle) {
|
|
capturedCss = appendedStyle.textContent;
|
|
}
|
|
|
|
assert.equal(gmAddStyleCalls, 1, "script should use GM_addStyle for stable Tampermonkey injection");
|
|
assert.equal(menuCommandName, "清除 Chaturbate 缩略图探测缓存", "script should expose a Tampermonkey cache-clear menu command");
|
|
assert.equal(appendedStyle, null, "script should not manually append a duplicate style when GM_addStyle succeeds");
|
|
assert.equal(fakeElement.id, "tm-thumb-scale-style");
|
|
assert.equal(observedTarget, fakeDocument.head, "style reinjection observer should watch document.head only");
|
|
assert.equal(domContentLoadedHandler, null, "script should not add extra DOMContentLoaded work when head already exists");
|
|
assert.match(
|
|
capturedCss,
|
|
/\.FollowedDropdown__rooms\s*(?:,|\{)/,
|
|
"followed dropdown rooms grid should be resized",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.FollowedDropdown__room-image\s*(?:,|\{)/,
|
|
"followed dropdown preview images should fill enlarged cards",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#roomlist_root\s+ul\.list:has\(li\.roomCard\)/,
|
|
"current homepage room lists should be enlarged",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#roomlist_root\s+ul\.RoomCardGrid/,
|
|
"current homepage RoomCardGrid lists should also be enlarged",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/data-tm-thumb-scale-following-list="1"[\s\S]*--tm-thumb-following-list-width/,
|
|
"followed-cams list should be separated from homepage list sizing",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#main\.roomPage\s+ul\.list:has\(li\.roomCard\)\s+\.room_thumbnail_container[\s\S]*width:\s*var\(--tm-thumb-related-thumb-width\)\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
|
|
"room page related rooms thumbnail containers should use detected thumb width and height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.BaseRoomContents\s+ul\.list:has\(li\.roomCard\)\s+\.room_thumbnail[\s\S]*width:\s*var\(--tm-thumb-related-thumb-width\)\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
|
|
"base room related thumbnails should use detected thumb width and height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.BaseRoomContents\s+ul\.RoomCardGrid[\s\S]*grid-template-columns:\s*repeat\(auto-fill,\s*minmax\(var\(--tm-thumb-related-width\),\s*1fr\)\)\s*!important/,
|
|
"base room RoomCardGrid related rooms should use detected width",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.BaseRoomContents\s+ul\.RoomCardGrid\s+\.RoomCardThumbnail[\s\S]*width:\s*var\(--tm-thumb-related-thumb-width\)\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
|
|
"base room RoomCardGrid thumbnails should use detected thumb width and height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.BaseRoomContents\s+ul\.RoomCardGrid\s*>\s*li\.RoomCard[\s\S]*height:\s*var\(--tm-thumb-related-card-height\)\s*!important/,
|
|
"base room RoomCardGrid related cards should use detected card height",
|
|
);
|
|
assert.doesNotMatch(capturedCss, /--tm-thumb-min-root:\s*\d+px;/, "script should not hard-code a root thumbnail fallback width");
|
|
assert.doesNotMatch(capturedCss, /--tm-thumb-height-root:\s*\d+px;/, "script should not hard-code a root thumbnail fallback height");
|
|
assert.match(
|
|
capturedCss,
|
|
/\.carousel-root\s+\.room-list-carousel\s+ul\.list\s*>\s*li/,
|
|
"discover carousel ul.list items should be enlarged",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#discover_root\s+\.room-list-carousel\s+\.room_thumbnail_container\s+img/,
|
|
"discover carousel thumbnail images should fill enlarged items",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/height:\s*var\(--tm-thumb-discover-triple-ul\)\s*!important/,
|
|
"discover carousel container heights should use detected original heights",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/height:\s*var\(--tm-thumb-discover-double-arrow\)\s*!important/,
|
|
"discover carousel arrow heights should use detected original heights",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#discover_root\s+\.room-list-carousel\s+\.room_thumbnail[\s\S]*height:\s*var\(--tm-thumb-discover-height\)\s*!important/,
|
|
"discover carousel images should use detected original height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#discover_root\s+\.room-list-carousel\s+\.room_thumbnail[\s\S]*width:\s*var\(--tm-thumb-discover-thumb-width\)\s*!important/,
|
|
"discover carousel images should use detected original thumb width",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/\.FollowedDropdown__room-image[\s\S]*width:\s*var\(--tm-thumb-follow-thumb-width\)\s*!important[\s\S]*height:\s*var\(--tm-thumb-follow-thumb-height\)\s*!important/,
|
|
"follow dropdown images should use detected original thumb width and height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/react-tooltip[\s\S]*width:\s*var\(--tm-thumb-tooltip-width\)\s*!important[\s\S]*height:\s*var\(--tm-thumb-tooltip-height\)\s*!important/,
|
|
"tooltip preview should use detected original image width and height",
|
|
);
|
|
assert.match(
|
|
capturedCss,
|
|
/#discover_root\s+\.room-list-carousel\s+ul\.list\s*>\s*li[\s\S]*height:\s*var\(--tm-thumb-discover-card-height\)\s*!important/,
|
|
"discover carousel cards should use detected original card height",
|
|
);
|
|
assert.doesNotMatch(source, /withOwnStyleDisabled/, "script should not disable its own style while measuring");
|
|
assert.match(source, /detectAndApplySizes/, "script should detect original sizes before applying scaled variables");
|
|
assert.match(source, /moduleReady/, "script should lock each module after detecting its original size");
|
|
assert.match(source, /CACHE_KEY/, "script should cache detected original sizes");
|
|
assert.match(source, /CARD_HEIGHT_SCALE/, "script should use a separate scale for card heights");
|
|
assert.match(source, /setCardHeightVar/, "script should apply card height scaling separately from thumbnail scaling");
|
|
assert.match(source, /localStorage\.setItem\(CACHE_KEY/, "script should save detected sizes in localStorage");
|
|
assert.match(source, /localStorage\.removeItem\(CACHE_KEY/, "script should clear cached sizes from the menu command");
|
|
assert.match(source, /scheduleMeasure\("after-800ms"\)/, "script should delay the first page measurement until original layout can render");
|
|
assert.doesNotMatch(
|
|
capturedCss,
|
|
/#desktop-spa-header\s*>\s*div\s*>\s*nav:nth-child/,
|
|
"script should avoid brittle header child-index selectors",
|
|
);
|
|
assert.doesNotMatch(capturedCss, /\.followedDropdown\b/, "old lowercase followed dropdown selectors should be removed");
|
|
|
|
const earlySource = source;
|
|
let earlyAppendedTarget = null;
|
|
let earlyDomContentLoadedHandler = null;
|
|
let earlyObservedTarget = null;
|
|
const earlyFakeDocument = {
|
|
documentElement: {
|
|
appendChild(node) {
|
|
earlyAppendedTarget = "documentElement";
|
|
this.node = node;
|
|
},
|
|
setAttribute() {},
|
|
},
|
|
head: null,
|
|
createElement() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
getElementById() {
|
|
return null;
|
|
},
|
|
addEventListener(type, handler) {
|
|
if (type === "DOMContentLoaded") {
|
|
earlyDomContentLoadedHandler = handler;
|
|
}
|
|
},
|
|
};
|
|
|
|
vm.runInNewContext(earlySource, {
|
|
console,
|
|
document: earlyFakeDocument,
|
|
GM_addStyle() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
GM_registerMenuCommand() {},
|
|
MutationObserver: class {
|
|
observe(target) {
|
|
earlyObservedTarget = target;
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.equal(earlyAppendedTarget, null, "GM_addStyle should avoid manual documentElement injection before head exists");
|
|
assert.equal(typeof earlyDomContentLoadedHandler, "function", "script should reinject after head becomes available");
|
|
assert.equal(earlyObservedTarget, null, "script should not observe a missing head");
|
|
|
|
const styleVars = new Map();
|
|
const attrs = new Map();
|
|
const cacheFakeDocument = {
|
|
documentElement: {
|
|
style: {
|
|
setProperty(name, value) {
|
|
styleVars.set(name, value);
|
|
},
|
|
},
|
|
setAttribute(name, value) {
|
|
attrs.set(name, value);
|
|
},
|
|
removeAttribute(name) {
|
|
attrs.delete(name);
|
|
},
|
|
},
|
|
head: {},
|
|
body: {},
|
|
createElement() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
getElementById() {
|
|
return null;
|
|
},
|
|
querySelectorAll() {
|
|
return [];
|
|
},
|
|
addEventListener() {},
|
|
};
|
|
const cacheStore = new Map([
|
|
["tm-thumb-scale:size-cache:v7", JSON.stringify({
|
|
schema: 7,
|
|
scale: 2,
|
|
cardHeightScale: 1.55,
|
|
modules: {
|
|
discover: {
|
|
discoverCard: { width: 182, height: 176 },
|
|
discoverThumb: { width: 180, height: 101 },
|
|
discoverTripleUl: { width: 1904, height: 550 },
|
|
discoverTripleArrow: { width: 35, height: 534 },
|
|
discoverDoubleUl: { width: 1904, height: 366 },
|
|
discoverDoubleArrow: { width: 35, height: 350 },
|
|
discoverSingleUl: { width: 2127, height: 182 },
|
|
discoverSingleArrow: { width: 35, height: 166 },
|
|
},
|
|
},
|
|
})],
|
|
]);
|
|
|
|
vm.runInNewContext(source, {
|
|
console,
|
|
document: cacheFakeDocument,
|
|
localStorage: {
|
|
getItem(key) {
|
|
return cacheStore.get(key) || null;
|
|
},
|
|
setItem(key, value) {
|
|
cacheStore.set(key, value);
|
|
},
|
|
removeItem(key) {
|
|
cacheStore.delete(key);
|
|
},
|
|
},
|
|
getComputedStyle() {
|
|
return {
|
|
getPropertyValue(name) {
|
|
return styleVars.get(name) || "";
|
|
},
|
|
};
|
|
},
|
|
GM_addStyle() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
GM_registerMenuCommand() {},
|
|
MutationObserver: class {
|
|
observe() {}
|
|
},
|
|
});
|
|
|
|
assert.equal(attrs.get("data-tm-thumb-scale-discover"), "1", "discover should be enabled directly from cached original sizes");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-width"), "364px");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-card-height"), "273px");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-thumb-width"), "360px");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-height"), "202px");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-single-ul"), "282px");
|
|
assert.equal(styleVars.get("--tm-thumb-discover-triple-ul"), "853px");
|
|
|
|
const followingVars = new Map();
|
|
const followingAttrs = new Map();
|
|
const followingFakeDocument = {
|
|
documentElement: {
|
|
style: {
|
|
setProperty(name, value) {
|
|
followingVars.set(name, value);
|
|
},
|
|
},
|
|
setAttribute(name, value) {
|
|
followingAttrs.set(name, value);
|
|
},
|
|
removeAttribute(name) {
|
|
followingAttrs.delete(name);
|
|
},
|
|
},
|
|
head: {},
|
|
body: {},
|
|
createElement() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
getElementById() {
|
|
return null;
|
|
},
|
|
querySelectorAll() {
|
|
return [];
|
|
},
|
|
addEventListener() {},
|
|
};
|
|
|
|
vm.runInNewContext(source, {
|
|
console,
|
|
document: followingFakeDocument,
|
|
location: { pathname: "/followed-cams/" },
|
|
localStorage: {
|
|
getItem() {
|
|
return JSON.stringify({
|
|
schema: 7,
|
|
scale: 2,
|
|
cardHeightScale: 1.55,
|
|
modules: {
|
|
home: {
|
|
homeCard: { width: 174, height: 120 },
|
|
homeThumb: { width: 174, height: 98 },
|
|
},
|
|
followingList: {
|
|
followingListCard: { width: 190, height: 120 },
|
|
followingListThumb: { width: 188, height: 106 },
|
|
},
|
|
},
|
|
});
|
|
},
|
|
setItem() {},
|
|
removeItem() {},
|
|
},
|
|
getComputedStyle() {
|
|
return {
|
|
getPropertyValue(name) {
|
|
return followingVars.get(name) || "";
|
|
},
|
|
};
|
|
},
|
|
GM_addStyle() {
|
|
return { id: "", textContent: "", setAttribute() {} };
|
|
},
|
|
GM_registerMenuCommand() {},
|
|
MutationObserver: class {
|
|
observe() {}
|
|
},
|
|
});
|
|
|
|
assert.equal(followingAttrs.get("data-tm-thumb-scale-following-list"), "1", "followed-cams should use its own cached list module");
|
|
assert.equal(followingAttrs.get("data-tm-thumb-scale-home"), undefined, "followed-cams should not apply homepage cache");
|
|
assert.equal(followingVars.get("--tm-thumb-following-list-width"), "380px");
|
|
assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), "186px");
|
|
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-width"), "376px");
|
|
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), "212px");
|
|
assert.equal(followingVars.get("--tm-thumb-home-width"), undefined);
|