mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-08-20 12:06:19 -04:00
410 lines
12 KiB
JavaScript
410 lines
12 KiB
JavaScript
const {
|
|
evaluateScript,
|
|
loadHtmlString
|
|
} = require("./helpers/extension-test-utils");
|
|
|
|
function bootSettingsCore() {
|
|
loadHtmlString("<!doctype html><html><head></head><body></body></html>");
|
|
evaluateScript("extension/shared/settings-core.js");
|
|
evaluateScript("extension/shared/site-rules.js");
|
|
}
|
|
|
|
function youtubeShortsRule(settings) {
|
|
return settings.siteRules.find(function (rule) {
|
|
return String(rule.pattern).includes("\\/shorts\\/");
|
|
});
|
|
}
|
|
|
|
describe("canonical settings storage", () => {
|
|
beforeEach(() => {
|
|
bootSettingsCore();
|
|
});
|
|
|
|
it("expands missing settings to the built-in Shorts remember-speed rule", () => {
|
|
const settings = window.vscExpandStoredSettings({});
|
|
const shortsRule = youtubeShortsRule(settings);
|
|
|
|
expect(shortsRule).toEqual(
|
|
expect.objectContaining({
|
|
title: "YouTube Shorts",
|
|
enabled: true,
|
|
rememberSpeed: true,
|
|
controllerMarginTop: 60,
|
|
controllerMarginBottom: 85
|
|
})
|
|
);
|
|
expect(settings.shortcutTargetMode).toBe("closest");
|
|
expect(settings.showAmbientLoopControls).toBe(false);
|
|
expect(settings.subtitleNudgeInterval).toBe(250);
|
|
expect(settings.siteRules[0].enableSubtitleNudge).toBeUndefined();
|
|
});
|
|
|
|
it("round-trips global and site-specific ambient-loop visibility", () => {
|
|
const settings = window.vscExpandStoredSettings({
|
|
showAmbientLoopControls: true,
|
|
siteRules: [
|
|
{
|
|
pattern: "cnn.com",
|
|
showAmbientLoopControls: false
|
|
}
|
|
]
|
|
});
|
|
const restored = window.vscExpandStoredSettings(
|
|
window.vscBuildStoredSettingsDiff(settings)
|
|
);
|
|
|
|
expect(restored.showAmbientLoopControls).toBe(true);
|
|
expect(restored.siteRules).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
pattern: "cnn.com",
|
|
showAmbientLoopControls: false
|
|
})
|
|
])
|
|
);
|
|
});
|
|
|
|
it("round-trips global and site-specific shortcut targeting", () => {
|
|
const settings = window.vscExpandStoredSettings({
|
|
shortcutTargetMode: "all",
|
|
siteRules: [
|
|
{
|
|
title: "One player at a time",
|
|
pattern: "example.org",
|
|
shortcutTargetMode: "closest"
|
|
}
|
|
]
|
|
});
|
|
const stored = window.vscBuildStoredSettingsDiff(settings);
|
|
const restored = window.vscExpandStoredSettings(stored);
|
|
|
|
expect(restored.shortcutTargetMode).toBe("all");
|
|
expect(restored.siteRules).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
pattern: "example.org",
|
|
shortcutTargetMode: "closest"
|
|
})
|
|
])
|
|
);
|
|
});
|
|
|
|
it("keeps every managed setting recognizable in legacy raw backups", () => {
|
|
evaluateScript("extension/shared/import-export.js");
|
|
const importExport = window.SpeederShared.importExport;
|
|
|
|
window.vscGetManagedSyncKeys().forEach(function (key) {
|
|
expect(importExport.isRecognizedRawSettingsObject({ [key]: null })).toBe(
|
|
true
|
|
);
|
|
});
|
|
});
|
|
|
|
it("provides titles for built-in video rules and round-trips title edits sparsely", () => {
|
|
const settings = window.vscExpandStoredSettings({});
|
|
expect(settings.siteRules.map((rule) => rule.title)).toEqual([
|
|
"YouTube videos",
|
|
"YouTube Shorts",
|
|
"YouTube Shorts (mobile)"
|
|
]);
|
|
|
|
settings.siteRules[1].title = "Short-form videos";
|
|
const stored = window.vscBuildStoredSettingsDiff(settings);
|
|
expect(stored.siteRules).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
pattern: settings.siteRules[1].pattern,
|
|
title: "Short-form videos"
|
|
})
|
|
])
|
|
);
|
|
expect(window.vscExpandStoredSettings(stored).siteRules[1].title).toBe(
|
|
"Short-form videos"
|
|
);
|
|
});
|
|
|
|
it("keeps an intentionally cleared built-in title cleared", () => {
|
|
const settings = window.vscExpandStoredSettings({});
|
|
const pattern = settings.siteRules[0].pattern;
|
|
delete settings.siteRules[0].title;
|
|
|
|
const stored = window.vscBuildStoredSettingsDiff(settings);
|
|
const tombstone = stored.siteRulesMeta.removedDefaultRuleKeys.find(
|
|
(entry) => entry.pattern === pattern
|
|
);
|
|
|
|
expect(tombstone.keys).toContain("title");
|
|
expect(window.vscExpandStoredSettings(stored).siteRules[0]).not.toHaveProperty(
|
|
"title"
|
|
);
|
|
});
|
|
|
|
it("round-trips an intentionally empty site-rule list", () => {
|
|
const expanded = window.vscExpandStoredSettings({ siteRules: [] });
|
|
expect(expanded.siteRules).toEqual([]);
|
|
|
|
const stored = window.vscBuildStoredSettingsDiff(expanded);
|
|
expect(stored.siteRulesFormat).toBe("defaults-diff-v2");
|
|
expect(stored.siteRules).toBeUndefined();
|
|
expect(stored.siteRulesMeta.removedDefaultPatterns).toHaveLength(3);
|
|
expect(window.vscExpandStoredSettings(stored).siteRules).toEqual([]);
|
|
});
|
|
|
|
it("keeps removals from built-in rules through sparse persistence", () => {
|
|
const settings = window.vscExpandStoredSettings({});
|
|
const shortsRule = youtubeShortsRule(settings);
|
|
delete shortsRule.rememberSpeed;
|
|
|
|
const stored = window.vscBuildStoredSettingsDiff(settings);
|
|
const tombstone = stored.siteRulesMeta.removedDefaultRuleKeys.find(
|
|
function (entry) {
|
|
return entry.pattern === shortsRule.pattern;
|
|
}
|
|
);
|
|
expect(tombstone.keys).toContain("rememberSpeed");
|
|
|
|
const restoredRule = youtubeShortsRule(
|
|
window.vscExpandStoredSettings(stored)
|
|
);
|
|
expect(restoredRule).not.toHaveProperty("rememberSpeed");
|
|
expect(restoredRule.controllerMarginTop).toBe(60);
|
|
expect(restoredRule.controllerMarginBottom).toBe(85);
|
|
});
|
|
|
|
it("expands legacy defaults-diff-v1 site-rule patches", () => {
|
|
const defaults = window.vscGetSettingsDefaults();
|
|
const shortsPattern = youtubeShortsRule(defaults).pattern;
|
|
const settings = window.vscExpandStoredSettings({
|
|
siteRulesFormat: "defaults-diff-v1",
|
|
siteRules: [{ pattern: shortsPattern, controllerMarginTop: 72 }]
|
|
});
|
|
const shortsRule = youtubeShortsRule(settings);
|
|
|
|
expect(shortsRule.rememberSpeed).toBe(true);
|
|
expect(shortsRule.controllerMarginTop).toBe(72);
|
|
expect(shortsRule.controllerMarginBottom).toBe(85);
|
|
});
|
|
|
|
it("migrates legacy shortcut, hide, and blacklist values", () => {
|
|
const settings = window.vscExpandStoredSettings({
|
|
speedStep: 0.25,
|
|
rewindTime: 7,
|
|
advanceTime: 14,
|
|
fastSpeed: 2.25,
|
|
displayKeyCode: 81,
|
|
fasterKeyCode: 70,
|
|
hideWithYouTubeControls: true,
|
|
blacklist: "bad.test\nhttps://blocked.test/watch"
|
|
});
|
|
const bindings = Object.fromEntries(
|
|
settings.keyBindings.map(function (binding) {
|
|
return [binding.action, binding];
|
|
})
|
|
);
|
|
|
|
expect(bindings.slower.value).toBe(0.25);
|
|
expect(bindings.faster).toEqual(
|
|
expect.objectContaining({ code: "KeyF", value: 0.25 })
|
|
);
|
|
expect(bindings.rewind.value).toBe(7);
|
|
expect(bindings.advance.value).toBe(14);
|
|
expect(bindings.fast.value).toBe(2.25);
|
|
expect(bindings.display.code).toBe("KeyQ");
|
|
expect(settings.hideWithControls).toBe(true);
|
|
expect(settings.siteRules).toEqual(
|
|
expect.arrayContaining([
|
|
{ pattern: "bad.test", enabled: false },
|
|
{ pattern: "https://blocked.test/watch", enabled: false }
|
|
])
|
|
);
|
|
});
|
|
|
|
it("adds the mobile Shorts default to legacy full default-rule lists", () => {
|
|
const defaults = window.vscGetSettingsDefaults();
|
|
const legacyRules = defaults.siteRules.slice(0, 2).map(function(rule) {
|
|
return Object.assign({}, rule, {
|
|
forceLastSavedSpeed: false,
|
|
audioBoolean: false
|
|
});
|
|
});
|
|
|
|
const expanded = window.vscExpandStoredSettings({ siteRules: legacyRules });
|
|
|
|
expect(
|
|
expanded.siteRules.some(function(rule) {
|
|
return String(rule.pattern).includes("m\\.youtube") &&
|
|
rule.rememberSpeed === true;
|
|
})
|
|
).toBe(true);
|
|
expect(expanded.siteRules.map((rule) => rule.title)).toEqual([
|
|
"YouTube videos",
|
|
"YouTube Shorts",
|
|
"YouTube Shorts (mobile)"
|
|
]);
|
|
});
|
|
|
|
it("normalizes imported rule types and unsafe numeric ranges", () => {
|
|
const expanded = window.vscExpandStoredSettings({
|
|
enabled: "false",
|
|
siteRules: [
|
|
{
|
|
title: " Example videos ",
|
|
pattern: "example.org",
|
|
enabled: "false",
|
|
rememberSpeed: "false",
|
|
forceLastSavedSpeed: "true",
|
|
controllerOpacity: 3,
|
|
subtitleNudgeInterval: -5,
|
|
preferredSpeed: 500
|
|
}
|
|
]
|
|
});
|
|
const rule = expanded.siteRules[0];
|
|
|
|
expect(expanded.enabled).toBe(false);
|
|
expect(rule.title).toBe("Example videos");
|
|
expect(rule.enabled).toBe(false);
|
|
expect(rule.rememberSpeed).toBe(false);
|
|
expect(rule.forceLastSavedSpeed).toBe(true);
|
|
expect(rule.controllerOpacity).toBe(1);
|
|
expect(rule.subtitleNudgeInterval).toBe(250);
|
|
expect(rule.preferredSpeed).toBe(16);
|
|
});
|
|
|
|
it("falls back for null and blank numeric settings instead of coercing zero", () => {
|
|
const expanded = window.vscExpandStoredSettings({
|
|
lastSpeed: null,
|
|
controllerMarginBottom: " ",
|
|
siteRules: [
|
|
{
|
|
pattern: "example.org",
|
|
preferredSpeed: "",
|
|
controllerMarginBottom: null
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(expanded.lastSpeed).toBe(1);
|
|
expect(expanded.controllerMarginBottom).toBe(65);
|
|
expect(expanded.siteRules[0].preferredSpeed).toBeUndefined();
|
|
expect(expanded.siteRules[0].controllerMarginBottom).toBeUndefined();
|
|
});
|
|
|
|
it("repairs invalid imported shortcut values to safe action defaults", () => {
|
|
const expanded = window.vscExpandStoredSettings({
|
|
keyBindings: [
|
|
{ action: "faster", code: "KeyD", value: 0 },
|
|
{ action: "fast", code: "KeyG", value: "not-a-number" },
|
|
{ action: "rewind", code: "KeyZ", value: -2 }
|
|
]
|
|
});
|
|
|
|
expect(expanded.keyBindings.map((binding) => binding.value)).toEqual([
|
|
0.1,
|
|
1.8,
|
|
10
|
|
]);
|
|
});
|
|
|
|
it("drops malformed primitive shortcut entries instead of crashing hydration", () => {
|
|
expect(() =>
|
|
window.vscExpandStoredSettings({
|
|
keyBindings: [null, "bad", 7, { action: "faster", code: "KeyD", value: 0.2 }]
|
|
})
|
|
).not.toThrow();
|
|
|
|
expect(
|
|
window.vscExpandStoredSettings({
|
|
keyBindings: [null, "bad", { action: "faster", code: "KeyD", value: 0.2 }]
|
|
}).keyBindings
|
|
).toEqual([
|
|
expect.objectContaining({ action: "faster", value: 0.2 })
|
|
]);
|
|
});
|
|
|
|
it("removes stale managed keys without touching runtime or future data", () => {
|
|
const raw = {
|
|
rememberSpeed: true,
|
|
speedStep: 0.2,
|
|
lastSpeed: 1.75,
|
|
futureRuntimeValue: { keep: true }
|
|
};
|
|
const settings = window.vscExpandStoredSettings(raw);
|
|
settings.rememberSpeed = false;
|
|
|
|
const mutation = window.vscBuildManagedStorageMutation(raw, settings);
|
|
expect(mutation.remove).toEqual(
|
|
expect.arrayContaining(["rememberSpeed", "speedStep"])
|
|
);
|
|
expect(mutation.remove).not.toContain("lastSpeed");
|
|
expect(mutation.remove).not.toContain("futureRuntimeValue");
|
|
expect(mutation.set.keyBindings).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ action: "faster", value: 0.2 })
|
|
])
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("site-rule matching", () => {
|
|
beforeEach(() => {
|
|
bootSettingsCore();
|
|
});
|
|
|
|
it("merges all matches in order so a later specific rule wins", () => {
|
|
const matched = window.vscMatchSiteRule(
|
|
"https://www.youtube.com/shorts/abc",
|
|
[
|
|
{
|
|
pattern: "youtube.com",
|
|
enabled: false,
|
|
rememberSpeed: false,
|
|
controllerOpacity: 0.2
|
|
},
|
|
{
|
|
pattern: "youtube.com/shorts",
|
|
enabled: true,
|
|
rememberSpeed: true
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(matched).toEqual(
|
|
expect.objectContaining({
|
|
enabled: true,
|
|
rememberSpeed: true,
|
|
controllerOpacity: 0.2
|
|
})
|
|
);
|
|
});
|
|
|
|
it("matches plain host patterns only on safe hostname boundaries", () => {
|
|
const matcher = window.SpeederShared.siteRules;
|
|
|
|
expect(
|
|
matcher.plainSitePatternMatchesUrl(
|
|
"example.com",
|
|
"https://media.example.com/watch"
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
matcher.plainSitePatternMatchesUrl(
|
|
"example.com",
|
|
"https://notexample.com/watch"
|
|
)
|
|
).toBe(false);
|
|
expect(
|
|
matcher.plainSitePatternMatchesUrl(
|
|
"example.com",
|
|
"https://example.com.evil.test/watch"
|
|
)
|
|
).toBe(false);
|
|
expect(
|
|
matcher.plainSitePatternMatchesUrl(
|
|
"example.com",
|
|
"https://safe.test/watch?next=example.com"
|
|
)
|
|
).toBe(false);
|
|
});
|
|
});
|