Files
Tampermonkey_scripts/tests/followed-dropdown-css.test.js
2026-06-02 01:19:17 +08:00

313 lines
11 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-columns/,
"followed-cams list should be separated from homepage list column count",
);
assert.match(
capturedCss,
/data-tm-thumb-scale-following-list="1"[\s\S]*grid-template-columns:\s*repeat\(var\(--tm-thumb-following-list-columns\),\s*minmax\(0,\s*1fr\)\)\s*!important/,
"followed-cams list should reduce the original column count and keep 1fr alignment",
);
assert.match(
capturedCss,
/#main\.roomPage\s+ul\.list:has\(li\.roomCard\)\s+\.room_thumbnail_container[\s\S]*width:\s*100%\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
"room page related rooms thumbnail containers should follow card width and use detected height",
);
assert.match(
capturedCss,
/\.BaseRoomContents\s+ul\.list:has\(li\.roomCard\)\s+\.room_thumbnail[\s\S]*width:\s*100%\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
"base room related thumbnails should follow card width and use detected height",
);
assert.match(
capturedCss,
/\.BaseRoomContents\s+ul\.RoomCardGrid[\s\S]*grid-template-columns:\s*repeat\(auto-fill,\s*var\(--tm-thumb-related-width\)\)\s*!important/,
"base room RoomCardGrid related rooms should use detected card width",
);
assert.match(
capturedCss,
/\.BaseRoomContents\s+ul\.RoomCardGrid\s+\.RoomCardThumbnail[\s\S]*width:\s*100%\s*!important[\s\S]*height:\s*var\(--tm-thumb-related-height\)\s*!important/,
"base room RoomCardGrid thumbnails should follow card width and use detected 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.match(
capturedCss,
/\.BaseRoomContents\s+ul\.RoomCardGrid\s*>\s*li\.RoomCard[\s\S]*width:\s*var\(--tm-thumb-related-width\)\s*!important/,
"base room RoomCardGrid related cards should use detected card width",
);
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*100%\s*!important[\s\S]*height:\s*var\(--tm-thumb-follow-thumb-height\)\s*!important/,
"follow dropdown images should follow fixed card width and use detected 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, /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.doesNotMatch(source, /localStorage\.setItem/, "script should not write detected sizes to localStorage");
assert.match(source, /LEGACY_CACHE_PREFIX/, "script should only keep legacy cache cleanup support");
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 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: 15,
scale: 2,
cardHeightScale: 1.55,
modules: {
home: {
homeColumns: 7,
homeCard: { width: 174, height: 120 },
homeThumb: { width: 174, height: 98 },
},
followingList: {
followingListColumns: 7,
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"), undefined, "followed-cams should not apply legacy cached list module");
assert.equal(followingAttrs.get("data-tm-thumb-scale-home"), undefined, "followed-cams should not apply legacy homepage cache");
assert.equal(followingVars.get("--tm-thumb-following-list-columns"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-card-height"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-width"), undefined);
assert.equal(followingVars.get("--tm-thumb-following-list-thumb-height"), undefined);
assert.equal(followingVars.get("--tm-thumb-home-columns"), undefined);