mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-08-21 20:32:07 -04:00
389 lines
11 KiB
JavaScript
389 lines
11 KiB
JavaScript
const {
|
|
createChromeMock,
|
|
evaluateScript,
|
|
flushAsyncWork,
|
|
loadHtmlString
|
|
} = require("./helpers/extension-test-utils");
|
|
|
|
function bootInject(options) {
|
|
const config = options || {};
|
|
|
|
loadHtmlString("<!doctype html><html><body></body></html>");
|
|
|
|
const chrome = createChromeMock({
|
|
syncData: config.syncData,
|
|
localData: config.localData
|
|
});
|
|
|
|
global.chrome = chrome;
|
|
window.chrome = chrome;
|
|
window.requestIdleCallback = (callback, opts) =>
|
|
setTimeout(
|
|
() =>
|
|
callback({
|
|
didTimeout: false,
|
|
timeRemaining() {
|
|
return 1;
|
|
}
|
|
}),
|
|
(opts && opts.timeout) || 0
|
|
);
|
|
window.cancelIdleCallback = (id) => clearTimeout(id);
|
|
|
|
evaluateScript("extension/shared/controller-utils.js");
|
|
evaluateScript("extension/shared/key-bindings.js");
|
|
evaluateScript("extension/shared/site-rules.js");
|
|
evaluateScript("extension/shared/ui-icons.js");
|
|
evaluateScript("extension/content/inject.js");
|
|
|
|
return chrome;
|
|
}
|
|
|
|
describe("inject.js helper logic", () => {
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.unstubAllGlobals();
|
|
delete global.chrome;
|
|
});
|
|
|
|
it("normalizes bindings from legacy formats", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
expect(
|
|
window.normalizeStoredBinding({
|
|
action: "faster",
|
|
key: "g",
|
|
value: 1.8,
|
|
force: false
|
|
}).code
|
|
).toBe("KeyG");
|
|
|
|
expect(
|
|
window.normalizeStoredBinding({
|
|
action: "pause",
|
|
code: null,
|
|
key: null,
|
|
keyCode: null,
|
|
value: 0
|
|
})
|
|
).toEqual({
|
|
action: "pause",
|
|
code: null,
|
|
disabled: true,
|
|
value: 0,
|
|
force: "false",
|
|
predefined: false
|
|
});
|
|
|
|
expect(window.defaultKeyBindings({ speedStep: 0.25, rewindTime: 5 })[0]).toEqual(
|
|
{
|
|
action: "slower",
|
|
code: "KeyS",
|
|
value: 0.25,
|
|
force: false,
|
|
predefined: true
|
|
}
|
|
);
|
|
});
|
|
|
|
it("clamps controller margins and ignores stale source-specific target speeds", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
expect(window.normalizeControllerMarginPx(250, 0)).toBe(200);
|
|
expect(window.normalizeControllerMarginPx(-5, 65)).toBe(0);
|
|
expect(window.normalizeControllerMarginPx("bad", 65)).toBe(65);
|
|
|
|
const staleVideo = {
|
|
currentSrc: "fresh.mp4",
|
|
vsc: {
|
|
targetSpeed: 1.75,
|
|
targetSpeedSourceKey: "old.mp4"
|
|
}
|
|
};
|
|
|
|
expect(window.getControllerTargetSpeed(staleVideo)).toBeNull();
|
|
|
|
window.tc.settings.rememberSpeed = true;
|
|
window.tc.settings.forceLastSavedSpeed = false;
|
|
window.tc.settings.lastSpeed = 1.3;
|
|
window.tc.settings.speeds = { "fresh.mp4": 1.6 };
|
|
|
|
expect(window.getRememberedSpeed({ currentSrc: "fresh.mp4" })).toBe(1.6);
|
|
expect(window.getDesiredSpeed(staleVideo)).toBe(1.6);
|
|
});
|
|
|
|
it("applies site rule overrides and detects disabled sites", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
window.tc.settings.siteRules = [{ pattern: "example.org", enabled: false }];
|
|
window.captureSiteRuleBase();
|
|
expect(window.applySiteRuleOverrides()).toBe(true);
|
|
|
|
window.resetSettingsFromSiteRuleBase();
|
|
window.tc.settings.siteRules = [
|
|
{
|
|
pattern: "example.org",
|
|
controllerLocation: "bottom-left",
|
|
controllerMarginTop: 300,
|
|
controllerMarginBottom: -10,
|
|
rememberSpeed: true
|
|
}
|
|
];
|
|
window.captureSiteRuleBase();
|
|
|
|
expect(window.applySiteRuleOverrides()).toBe(false);
|
|
expect(window.tc.settings.controllerLocation).toBe("bottom-left");
|
|
expect(window.tc.settings.controllerMarginTop).toBe(200);
|
|
expect(window.tc.settings.controllerMarginBottom).toBe(0);
|
|
expect(window.tc.settings.rememberSpeed).toBe(true);
|
|
|
|
window.resetSettingsFromSiteRuleBase();
|
|
window.tc.settings.shortcutTargetMode = "closest";
|
|
window.tc.settings.siteRules = [
|
|
{ pattern: "example.org", shortcutTargetMode: "all" }
|
|
];
|
|
window.captureSiteRuleBase();
|
|
window.applySiteRuleOverrides();
|
|
expect(window.tc.settings.shortcutTargetMode).toBe("all");
|
|
});
|
|
|
|
it("sizes and positions the controller host to the video bounds", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
const mount = document.createElement("div");
|
|
const video = document.createElement("video");
|
|
const wrapper = document.createElement("div");
|
|
wrapper.className = "vsc-controller";
|
|
mount.append(video, wrapper);
|
|
document.body.appendChild(mount);
|
|
|
|
Object.defineProperties(mount, {
|
|
offsetWidth: { value: 400 },
|
|
offsetHeight: { value: 240 },
|
|
clientLeft: { value: 2 },
|
|
clientTop: { value: 2 }
|
|
});
|
|
mount.getBoundingClientRect = () => ({
|
|
left: 100,
|
|
top: 50,
|
|
right: 500,
|
|
bottom: 290,
|
|
width: 400,
|
|
height: 240
|
|
});
|
|
video.getBoundingClientRect = () => ({
|
|
left: 140,
|
|
top: 70,
|
|
right: 460,
|
|
bottom: 250,
|
|
width: 320,
|
|
height: 180
|
|
});
|
|
|
|
window.positionControllerHost(wrapper, video, mount);
|
|
|
|
expect(wrapper.style.getPropertyValue("left")).toBe("38px");
|
|
expect(wrapper.style.getPropertyValue("top")).toBe("18px");
|
|
expect(wrapper.style.getPropertyValue("width")).toBe("320px");
|
|
expect(wrapper.style.getPropertyValue("height")).toBe("180px");
|
|
expect(wrapper.style.getPropertyPriority("width")).toBe("important");
|
|
});
|
|
|
|
it("does not mount the controller outside a player stacking boundary", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
const page = document.createElement("main");
|
|
const outsidePlayer = document.createElement("section");
|
|
const isolatedPlayer = document.createElement("div");
|
|
const videoParent = document.createElement("div");
|
|
const video = document.createElement("video");
|
|
isolatedPlayer.style.isolation = "isolate";
|
|
videoParent.appendChild(video);
|
|
isolatedPlayer.appendChild(videoParent);
|
|
outsidePlayer.appendChild(isolatedPlayer);
|
|
page.appendChild(outsidePlayer);
|
|
document.body.appendChild(page);
|
|
|
|
const rect = {
|
|
left: 10,
|
|
top: 100,
|
|
right: 650,
|
|
bottom: 460,
|
|
width: 640,
|
|
height: 360
|
|
};
|
|
[video, videoParent, isolatedPlayer, outsidePlayer].forEach((element) => {
|
|
element.getBoundingClientRect = () => rect;
|
|
});
|
|
|
|
expect(window.getControllerMount(video)).toBe(isolatedPlayer);
|
|
});
|
|
|
|
it("keeps the controller inside a nested fullscreen player subtree", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
const isolatedPlayer = document.createElement("div");
|
|
isolatedPlayer.style.isolation = "isolate";
|
|
const fullscreenPlayer = document.createElement("media-player");
|
|
const provider = document.createElement("media-provider");
|
|
const video = document.createElement("video");
|
|
provider.appendChild(video);
|
|
fullscreenPlayer.appendChild(provider);
|
|
isolatedPlayer.appendChild(fullscreenPlayer);
|
|
document.body.appendChild(isolatedPlayer);
|
|
|
|
const rect = {
|
|
left: 0,
|
|
top: 0,
|
|
right: 640,
|
|
bottom: 360,
|
|
width: 640,
|
|
height: 360
|
|
};
|
|
[video, provider, fullscreenPlayer, isolatedPlayer].forEach((element) => {
|
|
element.getBoundingClientRect = () => rect;
|
|
});
|
|
|
|
expect(window.getControllerMount(video)).toBe(isolatedPlayer);
|
|
expect(window.getControllerMount(video, fullscreenPlayer)).toBe(
|
|
fullscreenPlayer
|
|
);
|
|
});
|
|
|
|
it("remounts the controller on fullscreen entry and restores it on exit", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
const isolatedPlayer = document.createElement("div");
|
|
isolatedPlayer.style.isolation = "isolate";
|
|
const fullscreenPlayer = document.createElement("media-player");
|
|
const provider = document.createElement("media-provider");
|
|
const video = document.createElement("video");
|
|
const wrapper = document.createElement("div");
|
|
wrapper.className = "vsc-controller";
|
|
provider.appendChild(video);
|
|
fullscreenPlayer.appendChild(provider);
|
|
isolatedPlayer.append(fullscreenPlayer, wrapper);
|
|
document.body.appendChild(isolatedPlayer);
|
|
|
|
const rect = {
|
|
left: 0,
|
|
top: 0,
|
|
right: 640,
|
|
bottom: 360,
|
|
width: 640,
|
|
height: 360
|
|
};
|
|
[video, provider, fullscreenPlayer, isolatedPlayer].forEach((element) => {
|
|
element.getBoundingClientRect = () => rect;
|
|
});
|
|
|
|
const controller = {
|
|
video,
|
|
div: wrapper,
|
|
normalControllerMount: isolatedPlayer
|
|
};
|
|
window.setupControllerHostTracking(controller, wrapper, isolatedPlayer);
|
|
Object.defineProperty(document, "fullscreenElement", {
|
|
configurable: true,
|
|
value: fullscreenPlayer
|
|
});
|
|
|
|
expect(window.syncControllerFullscreenMount(controller)).toBe(true);
|
|
expect(wrapper.parentElement).toBe(fullscreenPlayer);
|
|
|
|
Object.defineProperty(document, "fullscreenElement", {
|
|
configurable: true,
|
|
value: null
|
|
});
|
|
expect(window.syncControllerFullscreenMount(controller)).toBe(true);
|
|
expect(wrapper.parentElement).toBe(isolatedPlayer);
|
|
|
|
wrapper.remove();
|
|
controller.controllerHostCleanup();
|
|
});
|
|
|
|
it("force-rescans media missed during initial hydration", async () => {
|
|
bootInject();
|
|
await flushAsyncWork(3);
|
|
|
|
const video = document.createElement("video");
|
|
const source = document.createElement("source");
|
|
source.src = "https://example.org/late-source.mp4";
|
|
video.appendChild(source);
|
|
document.body.appendChild(video);
|
|
|
|
expect(video.vsc).toBeUndefined();
|
|
window.initializeWhenReady(document, true);
|
|
|
|
expect(video.vsc).toBeDefined();
|
|
expect(video.vsc.div).toBeDefined();
|
|
});
|
|
|
|
it("only blocks page key handlers when the shortcut force option is enabled", async () => {
|
|
bootInject({
|
|
syncData: {
|
|
keyBindings: [
|
|
{
|
|
action: "faster",
|
|
code: "KeyD",
|
|
value: 0.1,
|
|
force: false
|
|
}
|
|
]
|
|
}
|
|
});
|
|
await flushAsyncWork(3);
|
|
|
|
const video = document.createElement("video");
|
|
video.src = "https://example.org/shortcut.mp4";
|
|
document.body.appendChild(video);
|
|
window.ensureController(video, document.body);
|
|
let pageKeydowns = 0;
|
|
let pageCaptureKeydowns = 0;
|
|
document.addEventListener(
|
|
"keydown",
|
|
function() {
|
|
pageCaptureKeydowns += 1;
|
|
},
|
|
true
|
|
);
|
|
document.body.addEventListener("keydown", function() {
|
|
pageKeydowns += 1;
|
|
});
|
|
|
|
const unblocked = new KeyboardEvent("keydown", {
|
|
key: "d",
|
|
code: "KeyD",
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
video.dispatchEvent(unblocked);
|
|
|
|
expect(video.playbackRate).toBeCloseTo(1.1, 5);
|
|
expect(pageKeydowns).toBe(1);
|
|
expect(pageCaptureKeydowns).toBe(1);
|
|
expect(unblocked.defaultPrevented).toBe(false);
|
|
|
|
window.tc.settings.keyBindings[0].force = "true";
|
|
const blocked = new KeyboardEvent("keydown", {
|
|
key: "d",
|
|
code: "KeyD",
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
video.dispatchEvent(blocked);
|
|
|
|
expect(video.playbackRate).toBeCloseTo(1.2, 5);
|
|
expect(pageKeydowns).toBe(1);
|
|
expect(pageCaptureKeydowns).toBe(1);
|
|
expect(blocked.defaultPrevented).toBe(true);
|
|
});
|
|
});
|