mirror of
https://github.com/SoPat712/Speeder.git
synced 2026-10-08 03:03:14 -04:00
Reorganize extension source layout
This commit is contained in:
47 files changed
+198
-315
No files matched your search
@@ -10,8 +10,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
WEB_EXT_IGNORE_FILES: scripts/**
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -20,7 +18,7 @@ jobs:
|
|||||||
run: npm install -g web-ext
|
run: npm install -g web-ext
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: web-ext lint
|
run: web-ext lint --source-dir extension
|
||||||
|
|
||||||
# Beta tag (v*-beta) → Sign as unlisted on AMO, attach signed XPI to GitHub Prerelease
|
# Beta tag (v*-beta) → Sign as unlisted on AMO, attach signed XPI to GitHub Prerelease
|
||||||
# Firefox blocks all unsigned XPIs, even for self-hosted installs — unlisted signing is required
|
# Firefox blocks all unsigned XPIs, even for self-hosted installs — unlisted signing is required
|
||||||
@@ -30,7 +28,7 @@ jobs:
|
|||||||
web-ext sign \
|
web-ext sign \
|
||||||
--api-key ${{ secrets.FIREFOX_API_KEY }} \
|
--api-key ${{ secrets.FIREFOX_API_KEY }} \
|
||||||
--api-secret ${{ secrets.FIREFOX_API_SECRET }} \
|
--api-secret ${{ secrets.FIREFOX_API_SECRET }} \
|
||||||
--source-dir . \
|
--source-dir extension \
|
||||||
--artifacts-dir web-ext-artifacts \
|
--artifacts-dir web-ext-artifacts \
|
||||||
--channel unlisted
|
--channel unlisted
|
||||||
|
|
||||||
@@ -70,6 +68,6 @@ jobs:
|
|||||||
web-ext sign \
|
web-ext sign \
|
||||||
--api-key ${{ secrets.FIREFOX_API_KEY }} \
|
--api-key ${{ secrets.FIREFOX_API_KEY }} \
|
||||||
--api-secret ${{ secrets.FIREFOX_API_SECRET }} \
|
--api-secret ${{ secrets.FIREFOX_API_SECRET }} \
|
||||||
--source-dir . \
|
--source-dir extension \
|
||||||
--artifacts-dir web-ext-artifacts \
|
--artifacts-dir web-ext-artifacts \
|
||||||
--channel listed
|
--channel listed
|
||||||
@@ -67,6 +67,16 @@ listens both for lower and upper case values (i.e. you can use
|
|||||||
key. This is not a perfect solution, as some sites may listen to both, but works
|
key. This is not a perfect solution, as some sites may listen to both, but works
|
||||||
most of the time.
|
most of the time.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
The unpacked extension root is `extension/`. Load that directory in
|
||||||
|
`about:debugging`, and run extension tooling against it, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test
|
||||||
|
npx --yes web-ext lint --source-dir extension
|
||||||
|
```
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### The video controls are not showing up?
|
### The video controls are not showing up?
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
import glob
|
|
||||||
import fnmatch
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
SCRIPT_NAME = os.path.basename(__file__)
|
|
||||||
TARGET_FILE = "manifest.json"
|
|
||||||
DEFAULT_EXCLUDE_FILES = {".DS_Store"}
|
|
||||||
DEFAULT_EXCLUDE_DIRS = {"__pycache__", "temp"}
|
|
||||||
DEFAULT_EXCLUDE_PATTERNS = {"._*", "*.pyc"}
|
|
||||||
|
|
||||||
|
|
||||||
def should_exclude(rel_path, exclude_files, exclude_dirs):
|
|
||||||
rel_path = os.path.normpath(rel_path)
|
|
||||||
path_parts = rel_path.split(os.sep)
|
|
||||||
file_name = path_parts[-1]
|
|
||||||
|
|
||||||
if file_name in DEFAULT_EXCLUDE_FILES or rel_path in DEFAULT_EXCLUDE_FILES:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if any(part in DEFAULT_EXCLUDE_DIRS for part in path_parts):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if file_name in exclude_files or rel_path in exclude_files:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if any(part in exclude_dirs for part in path_parts):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if any(fnmatch.fnmatch(file_name, pattern) for pattern in DEFAULT_EXCLUDE_PATTERNS):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def zip_folder(output_name, folder, exclude_files, exclude_dirs):
|
|
||||||
with zipfile.ZipFile(output_name, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
||||||
for root, dirs, files in os.walk(folder):
|
|
||||||
dirs[:] = [
|
|
||||||
d
|
|
||||||
for d in dirs
|
|
||||||
if not should_exclude(
|
|
||||||
os.path.relpath(os.path.join(root, d), folder),
|
|
||||||
exclude_files,
|
|
||||||
exclude_dirs,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
for file in files:
|
|
||||||
rel_path = os.path.relpath(os.path.join(root, file), folder)
|
|
||||||
if should_exclude(rel_path, exclude_files, exclude_dirs):
|
|
||||||
continue
|
|
||||||
zipf.write(os.path.join(root, file), arcname=rel_path)
|
|
||||||
|
|
||||||
|
|
||||||
def update_version_line(file_path, new_version):
|
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
updated = False
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
match = re.match(r'\s*"version":\s*"([^"]+)"', line)
|
|
||||||
if match:
|
|
||||||
old_version = match.group(1)
|
|
||||||
lines[i] = re.sub(
|
|
||||||
r'"version":\s*".+?"', f'"version": "{new_version}"', line
|
|
||||||
)
|
|
||||||
updated = True
|
|
||||||
print(
|
|
||||||
f"🛠️ Changed version in {file_path} from {old_version} ➜ {new_version}"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if updated:
|
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
|
||||||
f.writelines(lines)
|
|
||||||
else:
|
|
||||||
print(f"⚠️ No version line found in {file_path}.")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Step 0: Remove all existing .xpi files upfront
|
|
||||||
xpi_files = glob.glob("*.xpi")
|
|
||||||
for f in xpi_files:
|
|
||||||
try:
|
|
||||||
os.remove(f)
|
|
||||||
print(f"🗑️ Removed existing archive: {f}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Failed to remove {f}: {e}")
|
|
||||||
|
|
||||||
# Read current version from manifest.json
|
|
||||||
current_dir = os.getcwd()
|
|
||||||
manifest_path = os.path.join(current_dir, TARGET_FILE)
|
|
||||||
current_version = "unknown"
|
|
||||||
|
|
||||||
if os.path.exists(manifest_path):
|
|
||||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
||||||
for line in f:
|
|
||||||
match = re.match(r'\s*"version":\s*"([^"]+)"', line)
|
|
||||||
if match:
|
|
||||||
current_version = match.group(1)
|
|
||||||
break
|
|
||||||
|
|
||||||
print(f"📦 Current version: {current_version}")
|
|
||||||
base_version = input("Enter the new base version (e.g., 2.0.1): ").strip()
|
|
||||||
if not base_version:
|
|
||||||
print("❌ No version entered. Exiting.")
|
|
||||||
return
|
|
||||||
|
|
||||||
github_version = f"{base_version}.0"
|
|
||||||
|
|
||||||
# Step 1: Update manifest.json on disk to base_version (for Firefox)
|
|
||||||
if os.path.exists(manifest_path):
|
|
||||||
update_version_line(manifest_path, base_version)
|
|
||||||
else:
|
|
||||||
print(f"❌ {TARGET_FILE} not found. Aborting.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Step 2: Create videospeed-firefox.xpi (exclude script, .git, AND videospeed-firefox.xpi itself)
|
|
||||||
exclude_files = [SCRIPT_NAME, "videospeed-firefox.xpi"]
|
|
||||||
exclude_dirs = [".git"]
|
|
||||||
zip_folder("videospeed-firefox.xpi", current_dir, exclude_files, exclude_dirs)
|
|
||||||
print("✅ Created videospeed-firefox.xpi")
|
|
||||||
|
|
||||||
# Step 3: Re-scan for .xpi files after Firefox archive creation, exclude them for GitHub zip
|
|
||||||
current_xpi_files = set(glob.glob("*.xpi"))
|
|
||||||
exclude_temp_files = current_xpi_files.union({SCRIPT_NAME})
|
|
||||||
exclude_temp_dirs = set(exclude_dirs)
|
|
||||||
|
|
||||||
# Step 4: Create videospeed-github.xpi from temp folder with version bumped to .0
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
for item in os.listdir(current_dir):
|
|
||||||
if should_exclude(item, exclude_temp_files, exclude_temp_dirs):
|
|
||||||
continue
|
|
||||||
src = os.path.join(current_dir, item)
|
|
||||||
dst = os.path.join(temp_dir, item)
|
|
||||||
if os.path.isdir(src):
|
|
||||||
shutil.copytree(src, dst)
|
|
||||||
else:
|
|
||||||
shutil.copy2(src, dst)
|
|
||||||
|
|
||||||
temp_manifest = os.path.join(temp_dir, TARGET_FILE)
|
|
||||||
if os.path.exists(temp_manifest):
|
|
||||||
update_version_line(temp_manifest, github_version)
|
|
||||||
else:
|
|
||||||
print(f"⚠️ {TARGET_FILE} not found in temp folder.")
|
|
||||||
|
|
||||||
zip_folder(
|
|
||||||
"videospeed-github.xpi", temp_dir, exclude_files=[], exclude_dirs=[]
|
|
||||||
)
|
|
||||||
print("✅ Created videospeed-github.xpi")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
|
|||||||
chrome.runtime.onMessage.addListener(function (request) {
|
chrome.runtime.onMessage.addListener(function (request) {
|
||||||
if (request.action === "openOptions") {
|
if (request.action === "openOptions") {
|
||||||
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
|
chrome.tabs.create({ url: chrome.runtime.getURL("options/options.html") });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
File renamed without changes.
File renamed without changes.
@@ -60,6 +60,7 @@ var YT_NATIVE_MAX = 2.0;
|
|||||||
var YT_NATIVE_STEP = 0.05;
|
var YT_NATIVE_STEP = 0.05;
|
||||||
var vscObservedRoots = new WeakSet();
|
var vscObservedRoots = new WeakSet();
|
||||||
var vscConnectedScannedRoots = new WeakSet();
|
var vscConnectedScannedRoots = new WeakSet();
|
||||||
|
var vscInitializedDocuments = new Set();
|
||||||
var requestIdle =
|
var requestIdle =
|
||||||
typeof window.requestIdleCallback === "function"
|
typeof window.requestIdleCallback === "function"
|
||||||
? window.requestIdleCallback.bind(window)
|
? window.requestIdleCallback.bind(window)
|
||||||
@@ -1882,7 +1883,7 @@ function defineVideoController() {
|
|||||||
var shadow = wrapper.attachShadow({ mode: "open" });
|
var shadow = wrapper.attachShadow({ mode: "open" });
|
||||||
var shadowStylesheet = doc.createElement("link");
|
var shadowStylesheet = doc.createElement("link");
|
||||||
shadowStylesheet.rel = "stylesheet";
|
shadowStylesheet.rel = "stylesheet";
|
||||||
shadowStylesheet.href = chrome.runtime.getURL("shadow.css");
|
shadowStylesheet.href = chrome.runtime.getURL("content/shadow.css");
|
||||||
shadow.appendChild(shadowStylesheet);
|
shadow.appendChild(shadowStylesheet);
|
||||||
|
|
||||||
var controller = doc.createElement("div");
|
var controller = doc.createElement("div");
|
||||||
@@ -2253,8 +2254,6 @@ function setupListener(root) {
|
|||||||
root.vscRateListenerAttached = true;
|
root.vscRateListenerAttached = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var vscInitializedDocuments = new Set();
|
|
||||||
|
|
||||||
function clearPendingInitialization(doc) {
|
function clearPendingInitialization(doc) {
|
||||||
if (!doc || !doc.vscPendingInitializeHandler) return;
|
if (!doc || !doc.vscPendingInitializeHandler) return;
|
||||||
|
|
||||||
File renamed without changes.
@@ -16,13 +16,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/icon16.png",
|
"16": "assets/icons/icon16.png",
|
||||||
"48": "icons/icon48.png",
|
"48": "assets/icons/icon48.png",
|
||||||
"128": "icons/icon128.png"
|
"128": "assets/icons/icon128.png"
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"background.js"
|
"background/background.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
@@ -30,16 +30,16 @@
|
|||||||
"https://cdn.jsdelivr.net/*"
|
"https://cdn.jsdelivr.net/*"
|
||||||
],
|
],
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html",
|
"page": "options/options.html",
|
||||||
"open_in_tab": true
|
"open_in_tab": true
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"19": "icons/icon19.png",
|
"19": "assets/icons/icon19.png",
|
||||||
"38": "icons/icon38.png",
|
"38": "assets/icons/icon38.png",
|
||||||
"48": "icons/icon48.png"
|
"48": "assets/icons/icon48.png"
|
||||||
},
|
},
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup/popup.html"
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
@@ -56,19 +56,19 @@
|
|||||||
"https://meet.google.com/*"
|
"https://meet.google.com/*"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"inject.css"
|
"content/inject.css"
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"shared/controller-utils.js",
|
"shared/controller-utils.js",
|
||||||
"shared/key-bindings.js",
|
"shared/key-bindings.js",
|
||||||
"shared/site-rules.js",
|
"shared/site-rules.js",
|
||||||
"ui-icons.js",
|
"shared/ui-icons.js",
|
||||||
"inject.js"
|
"content/inject.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
"inject.css",
|
"content/inject.css",
|
||||||
"shadow.css"
|
"content/shadow.css"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -5,14 +5,14 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Speeder Settings</title>
|
<title>Speeder Settings</title>
|
||||||
<link rel="stylesheet" href="options.css" />
|
<link rel="stylesheet" href="options.css" />
|
||||||
<script src="shared/controller-utils.js"></script>
|
<script src="../shared/controller-utils.js"></script>
|
||||||
<script src="shared/key-bindings.js"></script>
|
<script src="../shared/key-bindings.js"></script>
|
||||||
<script src="shared/popup-controls.js"></script>
|
<script src="../shared/popup-controls.js"></script>
|
||||||
<script src="ui-icons.js"></script>
|
<script src="../shared/ui-icons.js"></script>
|
||||||
<script src="lucide-client.js"></script>
|
<script src="lucide-client.js"></script>
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
<script src="shared/import-export.js"></script>
|
<script src="../shared/import-export.js"></script>
|
||||||
<script src="importExport.js"></script>
|
<script src="import-export.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page-shell">
|
<div class="page-shell">
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="support-cta-kofi-img"
|
class="support-cta-kofi-img"
|
||||||
src="images/kofi_symbol.svg"
|
src="../assets/images/kofi_symbol.svg"
|
||||||
width="241"
|
width="241"
|
||||||
height="194"
|
height="194"
|
||||||
alt=""
|
alt=""
|
||||||
File renamed without changes.
File renamed without changes.
@@ -4,9 +4,9 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Speeder</title>
|
<title>Speeder</title>
|
||||||
<link rel="stylesheet" href="popup.css" />
|
<link rel="stylesheet" href="popup.css" />
|
||||||
<script src="shared/site-rules.js"></script>
|
<script src="../shared/site-rules.js"></script>
|
||||||
<script src="shared/popup-controls.js"></script>
|
<script src="../shared/popup-controls.js"></script>
|
||||||
<script src="ui-icons.js"></script>
|
<script src="../shared/ui-icons.js"></script>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
aria-label="Support on Ko-fi (opens in new tab)"
|
aria-label="Support on Ko-fi (opens in new tab)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/kofi_symbol.svg"
|
src="../assets/images/kofi_symbol.svg"
|
||||||
width="28"
|
width="28"
|
||||||
height="22"
|
height="22"
|
||||||
alt=""
|
alt=""
|
||||||
@@ -4,7 +4,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var siteRuleUtils = speederShared.siteRules || {};
|
var siteRuleUtils = speederShared.siteRules || {};
|
||||||
var popupControlUtils = speederShared.popupControls || {};
|
var popupControlUtils = speederShared.popupControls || {};
|
||||||
|
|
||||||
/* `label` is only used if ui-icons.js has no path for this action (fallback). */
|
/* `label` is only used if shared/ui-icons.js has no path for this action (fallback). */
|
||||||
var controllerButtonDefs = {
|
var controllerButtonDefs = {
|
||||||
rewind: { label: "", className: "rw" },
|
rewind: { label: "", className: "rw" },
|
||||||
slower: { label: "", className: "" },
|
slower: { label: "", className: "" },
|
||||||
@@ -128,7 +128,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var tabId = tabs[0].id;
|
var tabId = tabs[0].id;
|
||||||
chrome.tabs.executeScript(
|
chrome.tabs.executeScript(
|
||||||
tabId,
|
tabId,
|
||||||
{ allFrames: true, file: "frameSpeedSnapshot.js" },
|
{ allFrames: true, file: "content/frame-speed-snapshot.js" },
|
||||||
function (results) {
|
function (results) {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
sendToActiveTab({ action: "get_speed" }, function (response) {
|
sendToActiveTab({ action: "get_speed" }, function (response) {
|
||||||
@@ -192,7 +192,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
btn.addEventListener("click", function () {
|
btn.addEventListener("click", function () {
|
||||||
if (btnId === "settings") {
|
if (btnId === "settings") {
|
||||||
window.open(chrome.runtime.getURL("options.html"));
|
window.open(chrome.runtime.getURL("options/options.html"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendToActiveTab(
|
sendToActiveTab(
|
||||||
@@ -210,11 +210,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
var manifest = chrome.runtime.getManifest();
|
var manifest = chrome.runtime.getManifest();
|
||||||
var versionElement = document.querySelector("#app-version");
|
var versionElement = document.querySelector("#app-version");
|
||||||
if (versionElement) {
|
if (versionElement) {
|
||||||
versionElement.innerText = manifest.version;
|
versionElement.textContent = manifest.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector("#config").addEventListener("click", function () {
|
document.querySelector("#config").addEventListener("click", function () {
|
||||||
window.open(chrome.runtime.getURL("options.html"));
|
window.open(chrome.runtime.getURL("options/options.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector("#about").addEventListener("click", function () {
|
document.querySelector("#about").addEventListener("click", function () {
|
||||||
@@ -350,9 +350,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const suffix = `${enabled ? "" : "_disabled"}.png`;
|
const suffix = `${enabled ? "" : "_disabled"}.png`;
|
||||||
chrome.browserAction.setIcon({
|
chrome.browserAction.setIcon({
|
||||||
path: {
|
path: {
|
||||||
19: "icons/icon19" + suffix,
|
19: "assets/icons/icon19" + suffix,
|
||||||
38: "icons/icon38" + suffix,
|
38: "assets/icons/icon38" + suffix,
|
||||||
48: "icons/icon48" + suffix
|
48: "assets/icons/icon48" + suffix
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -366,12 +366,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
function setStatusMessage(str) {
|
function setStatusMessage(str) {
|
||||||
const status_element = document.querySelector("#status");
|
const status_element = document.querySelector("#status");
|
||||||
status_element.classList.toggle("hide", false);
|
status_element.classList.toggle("hide", false);
|
||||||
status_element.innerText = str;
|
status_element.textContent = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearStatusMessage() {
|
function clearStatusMessage() {
|
||||||
const status_element = document.querySelector("#status");
|
const status_element = document.querySelector("#status");
|
||||||
status_element.classList.toggle("hide", true);
|
status_element.classList.toggle("hide", true);
|
||||||
status_element.innerText = "";
|
status_element.textContent = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
File renamed without changes.
@@ -50,7 +50,7 @@
|
|||||||
/**
|
/**
|
||||||
* Local-only keys excluded from backup JSON. These are disposable caches
|
* Local-only keys excluded from backup JSON. These are disposable caches
|
||||||
* (e.g. Lucide tags.json) that bloat exports and are refetched when needed.
|
* (e.g. Lucide tags.json) that bloat exports and are refetched when needed.
|
||||||
* Keep in sync with lucide-client.js (LUCIDE_TAGS_CACHE_KEY + "At").
|
* Keep in sync with options/lucide-client.js (LUCIDE_TAGS_CACHE_KEY + "At").
|
||||||
*/
|
*/
|
||||||
var localSettingsKeysOmittedFromExport = [
|
var localSettingsKeysOmittedFromExport = [
|
||||||
"lucideTagsCacheV1",
|
"lucideTagsCacheV1",
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -76,7 +76,7 @@ function vscSanitizeSvgTree(svg) {
|
|||||||
n.remove();
|
n.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
svg.querySelectorAll("*").forEach(function (el) {
|
[svg].concat(Array.from(svg.querySelectorAll("*"))).forEach(function (el) {
|
||||||
for (var i = el.attributes.length - 1; i >= 0; i--) {
|
for (var i = el.attributes.length - 1; i >= 0; i--) {
|
||||||
var attr = el.attributes[i];
|
var attr = el.attributes[i];
|
||||||
var name = attr.name.toLowerCase();
|
var name = attr.name.toLowerCase();
|
||||||
@@ -94,7 +94,7 @@ function vscSanitizeSvgTree(svg) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
svg.setAttribute("xmlns", VSC_SVG_NS);
|
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", VSC_SVG_NS);
|
||||||
svg.setAttribute("aria-hidden", "true");
|
svg.setAttribute("aria-hidden", "true");
|
||||||
return svg;
|
return svg;
|
||||||
}
|
}
|
||||||
@@ -7,19 +7,20 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT="$(git rev-parse --show-toplevel)"
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
MANIFEST_PATH="extension/manifest.json"
|
||||||
|
|
||||||
manifest_version() {
|
manifest_version() {
|
||||||
python3 -c 'import json; print(json.load(open("manifest.json"))["version"])'
|
MANIFEST_PATH="$MANIFEST_PATH" python3 -c 'import json, os; print(json.load(open(os.environ["MANIFEST_PATH"]))["version"])'
|
||||||
}
|
}
|
||||||
|
|
||||||
bump_manifest() {
|
bump_manifest() {
|
||||||
local ver="$1"
|
local ver="$1"
|
||||||
VER="$ver" python3 <<'PY'
|
VER="$ver" MANIFEST_PATH="$MANIFEST_PATH" python3 <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
ver = os.environ["VER"]
|
ver = os.environ["VER"]
|
||||||
path = "manifest.json"
|
path = os.environ["MANIFEST_PATH"]
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
data["version"] = ver
|
data["version"] = ver
|
||||||
@@ -58,8 +59,8 @@ fi
|
|||||||
git checkout beta
|
git checkout beta
|
||||||
git pull origin beta
|
git pull origin beta
|
||||||
|
|
||||||
echo "Current version on beta (manifest.json): $(manifest_version)"
|
echo "Current version on beta ($MANIFEST_PATH): $(manifest_version)"
|
||||||
read -r -p "Release version for manifest.json + tag (e.g. 5.0.4): " SEMVER_IN
|
read -r -p "Release version for $MANIFEST_PATH + tag (e.g. 5.0.4): " SEMVER_IN
|
||||||
SEMVER="$(normalize_semver "$SEMVER_IN")"
|
SEMVER="$(normalize_semver "$SEMVER_IN")"
|
||||||
validate_semver "$SEMVER"
|
validate_semver "$SEMVER"
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ fi
|
|||||||
echo
|
echo
|
||||||
echo "This will:"
|
echo "This will:"
|
||||||
echo " 1. checkout main, merge --squash origin/beta (single release commit on main)"
|
echo " 1. checkout main, merge --squash origin/beta (single release commit on main)"
|
||||||
echo " 2. set manifest.json to $SEMVER in that commit (if anything else changed, it is included too)"
|
echo " 2. set $MANIFEST_PATH to $SEMVER in that commit (if anything else changed, it is included too)"
|
||||||
echo " 3. push origin main, create tag $TAG, push tag (triggers listed AMO submit)"
|
echo " 3. push origin main, create tag $TAG, push tag (triggers listed AMO submit)"
|
||||||
echo " 4. checkout dev (merge main→dev yourself if you want them aligned)"
|
echo " 4. checkout dev (merge main→dev yourself if you want them aligned)"
|
||||||
read -r -p "Continue? [y/N] " confirm
|
read -r -p "Continue? [y/N] " confirm
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT="$(git rev-parse --show-toplevel)"
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
MANIFEST_PATH="extension/manifest.json"
|
||||||
|
|
||||||
manifest_version() {
|
manifest_version() {
|
||||||
python3 -c 'import json; print(json.load(open("manifest.json"))["version"])'
|
MANIFEST_PATH="$MANIFEST_PATH" python3 -c 'import json, os; print(json.load(open(os.environ["MANIFEST_PATH"]))["version"])'
|
||||||
}
|
}
|
||||||
|
|
||||||
bump_manifest() {
|
bump_manifest() {
|
||||||
local ver="$1"
|
local ver="$1"
|
||||||
VER="$ver" python3 <<'PY'
|
VER="$ver" MANIFEST_PATH="$MANIFEST_PATH" python3 <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
ver = os.environ["VER"]
|
ver = os.environ["VER"]
|
||||||
path = "manifest.json"
|
path = os.environ["MANIFEST_PATH"]
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
data["version"] = ver
|
data["version"] = ver
|
||||||
@@ -57,8 +58,8 @@ fi
|
|||||||
git checkout dev
|
git checkout dev
|
||||||
git pull origin dev
|
git pull origin dev
|
||||||
|
|
||||||
echo "Current version in manifest.json: $(manifest_version)"
|
echo "Current version in $MANIFEST_PATH: $(manifest_version)"
|
||||||
read -r -p "New version for manifest.json (e.g. 5.0.4): " SEMVER_IN
|
read -r -p "New version for $MANIFEST_PATH (e.g. 5.0.4): " SEMVER_IN
|
||||||
SEMVER="$(normalize_semver "$SEMVER_IN")"
|
SEMVER="$(normalize_semver "$SEMVER_IN")"
|
||||||
validate_semver "$SEMVER"
|
validate_semver "$SEMVER"
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "This will:"
|
echo "This will:"
|
||||||
echo " 1. set manifest.json version to $SEMVER, commit on dev, push origin dev"
|
echo " 1. set $MANIFEST_PATH version to $SEMVER, commit on dev, push origin dev"
|
||||||
echo " 2. checkout beta, merge dev (no-ff), push origin beta"
|
echo " 2. checkout beta, merge dev (no-ff), push origin beta"
|
||||||
echo " 3. create tag $TAG and push it (triggers beta AMO + prerelease)"
|
echo " 3. create tag $TAG and push it (triggers beta AMO + prerelease)"
|
||||||
echo " 4. checkout dev (main is not modified)"
|
echo " 4. checkout dev (main is not modified)"
|
||||||
@@ -86,7 +87,7 @@ read -r -p "Continue? [y/N] " confirm
|
|||||||
echo "🚀 Releasing beta $TAG"
|
echo "🚀 Releasing beta $TAG"
|
||||||
|
|
||||||
bump_manifest "$SEMVER"
|
bump_manifest "$SEMVER"
|
||||||
git add manifest.json
|
git add "$MANIFEST_PATH"
|
||||||
git commit -m "Bump version to $SEMVER"
|
git commit -m "Bump version to $SEMVER"
|
||||||
git push origin dev
|
git push origin dev
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { JSDOM } = require("jsdom");
|
const { JSDOM } = require("jsdom");
|
||||||
const { vi } = require("vitest");
|
const vi = globalThis.vi;
|
||||||
|
|
||||||
const ROOT = path.resolve(__dirname, "..", "..");
|
const ROOT = path.resolve(__dirname, "..", "..");
|
||||||
|
|
||||||
@@ -118,9 +118,13 @@ async function flushAsyncWork(turns) {
|
|||||||
const count = turns || 2;
|
const count = turns || 2;
|
||||||
for (let i = 0; i < count; i += 1) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
if (vi && typeof vi.isFakeTimers === "function" && vi.isFakeTimers()) {
|
||||||
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
|
} else {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function pickStorageValues(data, keys) {
|
function pickStorageValues(data, keys) {
|
||||||
if (keys == null) return clone(data || {});
|
if (keys == null) return clone(data || {});
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import {
|
|||||||
} from "./helpers/browser.js";
|
} from "./helpers/browser.js";
|
||||||
|
|
||||||
async function setupImportExport(overrides = {}) {
|
async function setupImportExport(overrides = {}) {
|
||||||
loadHtml("options.html");
|
loadHtml("extension/options/options.html");
|
||||||
globalThis.chrome = createChromeMock(overrides);
|
globalThis.chrome = createChromeMock(overrides);
|
||||||
window.chrome = globalThis.chrome;
|
window.chrome = globalThis.chrome;
|
||||||
const restoreSpy = vi.fn();
|
const restoreSpy = vi.fn();
|
||||||
globalThis.restore_options = restoreSpy;
|
globalThis.restore_options = restoreSpy;
|
||||||
window.restore_options = restoreSpy;
|
window.restore_options = restoreSpy;
|
||||||
loadScript("shared/import-export.js");
|
loadScript("extension/shared/import-export.js");
|
||||||
loadScript("importExport.js");
|
loadScript("extension/options/import-export.js");
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
return globalThis.chrome;
|
return globalThis.chrome;
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-16
@@ -1,8 +1,8 @@
|
|||||||
const { afterEach, beforeEach, describe, expect, it, vi } = require("vitest");
|
|
||||||
const {
|
const {
|
||||||
createChromeMock,
|
createChromeMock,
|
||||||
evaluateScript,
|
evaluateScript,
|
||||||
flushAsyncWork,
|
flushAsyncWork,
|
||||||
|
fireDOMContentLoaded,
|
||||||
installCommonWindowMocks,
|
installCommonWindowMocks,
|
||||||
loadHtmlString
|
loadHtmlString
|
||||||
} = require("./helpers/extension-test-utils");
|
} = require("./helpers/extension-test-utils");
|
||||||
@@ -26,18 +26,38 @@ function bootImportExport(options) {
|
|||||||
global.chrome = chrome;
|
global.chrome = chrome;
|
||||||
window.chrome = chrome;
|
window.chrome = chrome;
|
||||||
|
|
||||||
|
class TestBlob {
|
||||||
|
constructor(parts, options) {
|
||||||
|
this.parts = parts;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
async text() {
|
||||||
|
return this.parts.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
global.Blob = TestBlob;
|
||||||
|
window.Blob = TestBlob;
|
||||||
|
|
||||||
const createObjectURL = vi.fn(() => "blob:test");
|
const createObjectURL = vi.fn(() => "blob:test");
|
||||||
const revokeObjectURL = vi.fn();
|
const revokeObjectURL = vi.fn();
|
||||||
vi.stubGlobal("URL", {
|
Object.defineProperty(window.URL, "createObjectURL", {
|
||||||
createObjectURL,
|
configurable: true,
|
||||||
revokeObjectURL
|
value: createObjectURL
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(window.URL, "revokeObjectURL", {
|
||||||
|
configurable: true,
|
||||||
|
value: revokeObjectURL
|
||||||
|
});
|
||||||
|
global.URL = window.URL;
|
||||||
|
|
||||||
evaluateScript("importExport.js");
|
evaluateScript("extension/shared/import-export.js");
|
||||||
|
evaluateScript("extension/options/import-export.js");
|
||||||
|
fireDOMContentLoaded();
|
||||||
return { chrome, createObjectURL, revokeObjectURL };
|
return { chrome, createObjectURL, revokeObjectURL };
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("importExport.js", () => {
|
describe("options/import-export.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
});
|
});
|
||||||
@@ -53,14 +73,15 @@ describe("importExport.js", () => {
|
|||||||
bootImportExport();
|
bootImportExport();
|
||||||
|
|
||||||
expect(window.generateBackupFilename()).toBe(
|
expect(window.generateBackupFilename()).toBe(
|
||||||
"speeder-backup_2026-04-04_13.14.15.json"
|
"speeder-backup_2026-04-04_09.14.15.json"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exports sync and local settings into a downloadable backup", async () => {
|
it("exports sync and local settings into a downloadable backup", async () => {
|
||||||
const clickSpy = vi
|
Object.defineProperty(window.HTMLAnchorElement.prototype, "click", {
|
||||||
.spyOn(window.HTMLAnchorElement.prototype, "click")
|
configurable: true,
|
||||||
.mockImplementation(() => {});
|
value: vi.fn()
|
||||||
|
});
|
||||||
const { createObjectURL, revokeObjectURL } = bootImportExport({
|
const { createObjectURL, revokeObjectURL } = bootImportExport({
|
||||||
syncData: {
|
syncData: {
|
||||||
rememberSpeed: true,
|
rememberSpeed: true,
|
||||||
@@ -74,7 +95,6 @@ describe("importExport.js", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector("#exportSettings").click();
|
document.querySelector("#exportSettings").click();
|
||||||
await flushAsyncWork();
|
|
||||||
|
|
||||||
expect(createObjectURL).toHaveBeenCalledTimes(1);
|
expect(createObjectURL).toHaveBeenCalledTimes(1);
|
||||||
const blob = createObjectURL.mock.calls[0][0];
|
const blob = createObjectURL.mock.calls[0][0];
|
||||||
@@ -82,15 +102,15 @@ describe("importExport.js", () => {
|
|||||||
|
|
||||||
expect(backup.settings.rememberSpeed).toBe(true);
|
expect(backup.settings.rememberSpeed).toBe(true);
|
||||||
expect(backup.localSettings.customButtonIcons.faster.slug).toBe("rocket");
|
expect(backup.localSettings.customButtonIcons.faster.slug).toBe("rocket");
|
||||||
expect(clickSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(revokeObjectURL).toHaveBeenCalledWith("blob:test");
|
expect(revokeObjectURL).toHaveBeenCalledWith("blob:test");
|
||||||
expect(document.querySelector("#status").textContent).toContain("exported");
|
expect(document.querySelector("#status").textContent).toContain("exported");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("omits Lucide tags cache from exported localSettings", async () => {
|
it("omits Lucide tags cache from exported localSettings", async () => {
|
||||||
vi.spyOn(window.HTMLAnchorElement.prototype, "click").mockImplementation(
|
Object.defineProperty(window.HTMLAnchorElement.prototype, "click", {
|
||||||
() => {}
|
configurable: true,
|
||||||
);
|
value: vi.fn()
|
||||||
|
});
|
||||||
const { createObjectURL } = bootImportExport({
|
const { createObjectURL } = bootImportExport({
|
||||||
syncData: { rememberSpeed: true },
|
syncData: { rememberSpeed: true },
|
||||||
localData: {
|
localData: {
|
||||||
@@ -103,7 +123,6 @@ describe("importExport.js", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector("#exportSettings").click();
|
document.querySelector("#exportSettings").click();
|
||||||
await flushAsyncWork();
|
|
||||||
|
|
||||||
const blob = createObjectURL.mock.calls[0][0];
|
const blob = createObjectURL.mock.calls[0][0];
|
||||||
const backup = JSON.parse(await blob.text());
|
const backup = JSON.parse(await blob.text());
|
||||||
@@ -159,6 +178,7 @@ describe("importExport.js", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vi.stubGlobal("FileReader", FakeFileReader);
|
vi.stubGlobal("FileReader", FakeFileReader);
|
||||||
|
window.FileReader = FakeFileReader;
|
||||||
|
|
||||||
document.querySelector("#importSettings").click();
|
document.querySelector("#importSettings").click();
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
@@ -208,6 +228,7 @@ describe("importExport.js", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vi.stubGlobal("FileReader", FakeFileReader);
|
vi.stubGlobal("FileReader", FakeFileReader);
|
||||||
|
window.FileReader = FakeFileReader;
|
||||||
|
|
||||||
document.querySelector("#importSettings").click();
|
document.querySelector("#importSettings").click();
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const { afterEach, describe, expect, it, vi } = require("vitest");
|
|
||||||
const {
|
const {
|
||||||
createChromeMock,
|
createChromeMock,
|
||||||
evaluateScript,
|
evaluateScript,
|
||||||
@@ -31,8 +30,11 @@ function bootInject(options) {
|
|||||||
);
|
);
|
||||||
window.cancelIdleCallback = (id) => clearTimeout(id);
|
window.cancelIdleCallback = (id) => clearTimeout(id);
|
||||||
|
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/controller-utils.js");
|
||||||
evaluateScript("inject.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;
|
return chrome;
|
||||||
}
|
}
|
||||||
@@ -116,14 +118,14 @@ describe("inject.js helper logic", () => {
|
|||||||
bootInject();
|
bootInject();
|
||||||
await flushAsyncWork(3);
|
await flushAsyncWork(3);
|
||||||
|
|
||||||
window.tc.settings.siteRules = [{ pattern: "localhost", enabled: false }];
|
window.tc.settings.siteRules = [{ pattern: "example.org", enabled: false }];
|
||||||
window.captureSiteRuleBase();
|
window.captureSiteRuleBase();
|
||||||
expect(window.applySiteRuleOverrides()).toBe(true);
|
expect(window.applySiteRuleOverrides()).toBe(true);
|
||||||
|
|
||||||
window.resetSettingsFromSiteRuleBase();
|
window.resetSettingsFromSiteRuleBase();
|
||||||
window.tc.settings.siteRules = [
|
window.tc.settings.siteRules = [
|
||||||
{
|
{
|
||||||
pattern: "localhost",
|
pattern: "example.org",
|
||||||
controllerLocation: "bottom-left",
|
controllerLocation: "bottom-left",
|
||||||
controllerMarginTop: 300,
|
controllerMarginTop: 300,
|
||||||
controllerMarginBottom: -10,
|
controllerMarginBottom: -10,
|
||||||
|
|||||||
+10
-11
@@ -38,11 +38,11 @@ async function bootInject({ sync = {}, local = {} } = {}) {
|
|||||||
);
|
);
|
||||||
globalThis.cancelIdleCallback = (id) => clearTimeout(id);
|
globalThis.cancelIdleCallback = (id) => clearTimeout(id);
|
||||||
|
|
||||||
loadScript("shared/controller-utils.js");
|
loadScript("extension/shared/controller-utils.js");
|
||||||
loadScript("shared/key-bindings.js");
|
loadScript("extension/shared/key-bindings.js");
|
||||||
loadScript("shared/site-rules.js");
|
loadScript("extension/shared/site-rules.js");
|
||||||
loadScript("ui-icons.js");
|
loadScript("extension/shared/ui-icons.js");
|
||||||
loadScript("inject.js");
|
loadScript("extension/content/inject.js");
|
||||||
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
@@ -215,11 +215,11 @@ describe("inject runtime", () => {
|
|||||||
Promise.resolve().then(() => originalLocalGet(keys, callback));
|
Promise.resolve().then(() => originalLocalGet(keys, callback));
|
||||||
});
|
});
|
||||||
|
|
||||||
loadScript("shared/controller-utils.js");
|
loadScript("extension/shared/controller-utils.js");
|
||||||
loadScript("shared/key-bindings.js");
|
loadScript("extension/shared/key-bindings.js");
|
||||||
loadScript("shared/site-rules.js");
|
loadScript("extension/shared/site-rules.js");
|
||||||
loadScript("ui-icons.js");
|
loadScript("extension/shared/ui-icons.js");
|
||||||
loadScript("inject.js");
|
loadScript("extension/content/inject.js");
|
||||||
|
|
||||||
// Fast-forward 3000ms for delayed rescan to trigger
|
// Fast-forward 3000ms for delayed rescan to trigger
|
||||||
vi.advanceTimersByTime(3000);
|
vi.advanceTimersByTime(3000);
|
||||||
@@ -235,4 +235,3 @@ describe("inject runtime", () => {
|
|||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const { afterEach, describe, expect, it } = require("vitest");
|
|
||||||
const {
|
const {
|
||||||
evaluateScript,
|
evaluateScript,
|
||||||
loadHtmlString
|
loadHtmlString
|
||||||
@@ -11,8 +10,8 @@ describe("lucide-client.js", () => {
|
|||||||
|
|
||||||
it("builds icon URLs and rejects invalid slugs", () => {
|
it("builds icon URLs and rejects invalid slugs", () => {
|
||||||
loadHtmlString("<!doctype html><html><body></body></html>");
|
loadHtmlString("<!doctype html><html><body></body></html>");
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/ui-icons.js");
|
||||||
evaluateScript("lucide-client.js");
|
evaluateScript("extension/options/lucide-client.js");
|
||||||
|
|
||||||
expect(window.lucideIconSvgUrl("alarm-clock")).toContain(
|
expect(window.lucideIconSvgUrl("alarm-clock")).toContain(
|
||||||
"/icons/alarm-clock.svg"
|
"/icons/alarm-clock.svg"
|
||||||
@@ -23,8 +22,8 @@ describe("lucide-client.js", () => {
|
|||||||
|
|
||||||
it("sanitizes SVG before persisting a Lucide icon", () => {
|
it("sanitizes SVG before persisting a Lucide icon", () => {
|
||||||
loadHtmlString("<!doctype html><html><body></body></html>");
|
loadHtmlString("<!doctype html><html><body></body></html>");
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/ui-icons.js");
|
||||||
evaluateScript("lucide-client.js");
|
evaluateScript("extension/options/lucide-client.js");
|
||||||
|
|
||||||
const sanitized = window.sanitizeLucideSvg(`
|
const sanitized = window.sanitizeLucideSvg(`
|
||||||
<svg width="10" height="10" onclick="evil()">
|
<svg width="10" height="10" onclick="evil()">
|
||||||
@@ -43,8 +42,8 @@ describe("lucide-client.js", () => {
|
|||||||
|
|
||||||
it("searches and ranks icon slugs by query", () => {
|
it("searches and ranks icon slugs by query", () => {
|
||||||
loadHtmlString("<!doctype html><html><body></body></html>");
|
loadHtmlString("<!doctype html><html><body></body></html>");
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/ui-icons.js");
|
||||||
evaluateScript("lucide-client.js");
|
evaluateScript("extension/options/lucide-client.js");
|
||||||
|
|
||||||
const results = window.searchLucideSlugs(
|
const results = window.searchLucideSlugs(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import {
|
|||||||
} from "./helpers/browser.js";
|
} from "./helpers/browser.js";
|
||||||
|
|
||||||
async function setupOptions(overrides = {}) {
|
async function setupOptions(overrides = {}) {
|
||||||
loadHtml("options.html");
|
loadHtml("extension/options/options.html");
|
||||||
globalThis.chrome = createChromeMock(overrides);
|
globalThis.chrome = createChromeMock(overrides);
|
||||||
window.chrome = globalThis.chrome;
|
window.chrome = globalThis.chrome;
|
||||||
globalThis.fetch = vi.fn();
|
globalThis.fetch = vi.fn();
|
||||||
loadScript("shared/controller-utils.js");
|
loadScript("extension/shared/controller-utils.js");
|
||||||
loadScript("shared/key-bindings.js");
|
loadScript("extension/shared/key-bindings.js");
|
||||||
loadScript("shared/popup-controls.js");
|
loadScript("extension/shared/popup-controls.js");
|
||||||
loadScript("ui-icons.js");
|
loadScript("extension/shared/ui-icons.js");
|
||||||
loadScript("lucide-client.js");
|
loadScript("extension/options/lucide-client.js");
|
||||||
loadScript("options.js");
|
loadScript("extension/options/options.js");
|
||||||
triggerDomContentLoaded();
|
triggerDomContentLoaded();
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
return globalThis.chrome;
|
return globalThis.chrome;
|
||||||
|
|||||||
+16
-10
@@ -1,4 +1,3 @@
|
|||||||
const { afterEach, beforeEach, describe, expect, it, vi } = require("vitest");
|
|
||||||
const {
|
const {
|
||||||
createChromeMock,
|
createChromeMock,
|
||||||
evaluateScript,
|
evaluateScript,
|
||||||
@@ -11,7 +10,7 @@ const {
|
|||||||
function bootOptions(options) {
|
function bootOptions(options) {
|
||||||
const config = options || {};
|
const config = options || {};
|
||||||
|
|
||||||
loadHtmlFile("options.html");
|
loadHtmlFile("extension/options/options.html");
|
||||||
installCommonWindowMocks();
|
installCommonWindowMocks();
|
||||||
|
|
||||||
const chrome = createChromeMock({
|
const chrome = createChromeMock({
|
||||||
@@ -31,9 +30,12 @@ function bootOptions(options) {
|
|||||||
);
|
);
|
||||||
window.fetch = global.fetch;
|
window.fetch = global.fetch;
|
||||||
|
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/controller-utils.js");
|
||||||
evaluateScript("lucide-client.js");
|
evaluateScript("extension/shared/key-bindings.js");
|
||||||
evaluateScript("options.js");
|
evaluateScript("extension/shared/popup-controls.js");
|
||||||
|
evaluateScript("extension/shared/ui-icons.js");
|
||||||
|
evaluateScript("extension/options/lucide-client.js");
|
||||||
|
evaluateScript("extension/options/options.js");
|
||||||
fireDOMContentLoaded();
|
fireDOMContentLoaded();
|
||||||
|
|
||||||
return chrome;
|
return chrome;
|
||||||
@@ -119,7 +121,8 @@ describe("options.js", () => {
|
|||||||
window.populatePopupControlBarEditor(["advance", "settings", "rewind"]);
|
window.populatePopupControlBarEditor(["advance", "settings", "rewind"]);
|
||||||
|
|
||||||
window.createSiteRule({ pattern: "youtube.com" });
|
window.createSiteRule({ pattern: "youtube.com" });
|
||||||
const ruleEl = document.querySelector(".site-rule");
|
const siteRuleEls = document.querySelectorAll(".site-rule");
|
||||||
|
const ruleEl = siteRuleEls[siteRuleEls.length - 1];
|
||||||
ruleEl.querySelector(".override-placement").checked = true;
|
ruleEl.querySelector(".override-placement").checked = true;
|
||||||
ruleEl.querySelector(".site-controllerLocation").value = "top-right";
|
ruleEl.querySelector(".site-controllerLocation").value = "top-right";
|
||||||
ruleEl.querySelector(".site-controllerMarginTop").value = "300";
|
ruleEl.querySelector(".site-controllerMarginTop").value = "300";
|
||||||
@@ -167,8 +170,10 @@ describe("options.js", () => {
|
|||||||
expect(savedSettings.controllerMarginTop).toBe(200);
|
expect(savedSettings.controllerMarginTop).toBe(200);
|
||||||
expect(savedSettings.controllerMarginBottom).toBe(0);
|
expect(savedSettings.controllerMarginBottom).toBe(0);
|
||||||
expect(savedSettings.popupControllerButtons).toEqual(["advance", "rewind"]);
|
expect(savedSettings.popupControllerButtons).toEqual(["advance", "rewind"]);
|
||||||
expect(savedSettings.siteRules).toEqual([
|
expect(savedSettings.siteRules).toHaveLength(3);
|
||||||
{
|
expect(savedSettings.siteRules).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
pattern: "youtube.com",
|
pattern: "youtube.com",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
controllerLocation: "top-right",
|
controllerLocation: "top-right",
|
||||||
@@ -178,8 +183,9 @@ describe("options.js", () => {
|
|||||||
hideWithControlsTimer: 0.1,
|
hideWithControlsTimer: 0.1,
|
||||||
showPopupControlBar: false,
|
showPopupControlBar: false,
|
||||||
popupControllerButtons: ["advance", "rewind"]
|
popupControllerButtons: ["advance", "rewind"]
|
||||||
}
|
})
|
||||||
]);
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("blocks save when a site rule regex is invalid", async () => {
|
it("blocks save when a site rule regex is invalid", async () => {
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import {
|
|||||||
} from "./helpers/browser.js";
|
} from "./helpers/browser.js";
|
||||||
|
|
||||||
async function setupPopup(overrides = {}) {
|
async function setupPopup(overrides = {}) {
|
||||||
loadHtml("popup.html");
|
loadHtml("extension/popup/popup.html");
|
||||||
globalThis.chrome = createChromeMock(overrides);
|
globalThis.chrome = createChromeMock(overrides);
|
||||||
window.chrome = globalThis.chrome;
|
window.chrome = globalThis.chrome;
|
||||||
loadScript("shared/site-rules.js");
|
loadScript("extension/shared/site-rules.js");
|
||||||
loadScript("shared/popup-controls.js");
|
loadScript("extension/shared/popup-controls.js");
|
||||||
loadScript("ui-icons.js");
|
loadScript("extension/shared/ui-icons.js");
|
||||||
loadScript("popup.js");
|
loadScript("extension/popup/popup.js");
|
||||||
triggerDomContentLoaded();
|
triggerDomContentLoaded();
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
return globalThis.chrome;
|
return globalThis.chrome;
|
||||||
@@ -29,7 +29,7 @@ describe("popup UI", () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(document.getElementById("app-version").innerText).toBe("5.1.7.0");
|
expect(document.getElementById("app-version").textContent).toBe("5.1.7.0");
|
||||||
expect(document.getElementById("popupSpeed").textContent).toBe("1.75");
|
expect(document.getElementById("popupSpeed").textContent).toBe("1.75");
|
||||||
expect(
|
expect(
|
||||||
document.querySelectorAll("#popupControlBar button").length
|
document.querySelectorAll("#popupControlBar button").length
|
||||||
@@ -60,7 +60,7 @@ describe("popup UI", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(document.getElementById("status").innerText).toBe(
|
expect(document.getElementById("status").textContent).toBe(
|
||||||
"Speeder is disabled for this site."
|
"Speeder is disabled for this site."
|
||||||
);
|
);
|
||||||
expect(document.getElementById("popupSpeed").textContent).toBe("1.00");
|
expect(document.getElementById("popupSpeed").textContent).toBe("1.00");
|
||||||
@@ -85,9 +85,9 @@ describe("popup UI", () => {
|
|||||||
);
|
);
|
||||||
expect(chrome.browserAction.setIcon).toHaveBeenCalledWith({
|
expect(chrome.browserAction.setIcon).toHaveBeenCalledWith({
|
||||||
path: {
|
path: {
|
||||||
19: "icons/icon19_disabled.png",
|
19: "assets/icons/icon19_disabled.png",
|
||||||
38: "icons/icon38_disabled.png",
|
38: "assets/icons/icon38_disabled.png",
|
||||||
48: "icons/icon48_disabled.png"
|
48: "assets/icons/icon48_disabled.png"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -105,13 +105,13 @@ describe("popup UI", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("refresh").click();
|
document.getElementById("refresh").click();
|
||||||
expect(document.getElementById("status").innerText).toBe(
|
expect(document.getElementById("status").textContent).toBe(
|
||||||
"Cannot run on this page."
|
"Cannot run on this page."
|
||||||
);
|
);
|
||||||
|
|
||||||
response = { status: "complete" };
|
response = { status: "complete" };
|
||||||
document.getElementById("refresh").click();
|
document.getElementById("refresh").click();
|
||||||
expect(document.getElementById("status").innerText).toBe(
|
expect(document.getElementById("status").textContent).toBe(
|
||||||
"Scan complete. Closing..."
|
"Scan complete. Closing..."
|
||||||
);
|
);
|
||||||
vi.advanceTimersByTime(500);
|
vi.advanceTimersByTime(500);
|
||||||
|
|||||||
+20
-20
@@ -1,4 +1,3 @@
|
|||||||
const { afterEach, beforeEach, describe, expect, it, vi } = require("vitest");
|
|
||||||
const {
|
const {
|
||||||
createChromeMock,
|
createChromeMock,
|
||||||
evaluateScript,
|
evaluateScript,
|
||||||
@@ -11,7 +10,7 @@ const {
|
|||||||
function bootPopup(options) {
|
function bootPopup(options) {
|
||||||
const config = options || {};
|
const config = options || {};
|
||||||
|
|
||||||
loadHtmlFile("popup.html");
|
loadHtmlFile("extension/popup/popup.html");
|
||||||
installCommonWindowMocks();
|
installCommonWindowMocks();
|
||||||
|
|
||||||
const chrome = createChromeMock({
|
const chrome = createChromeMock({
|
||||||
@@ -47,8 +46,10 @@ function bootPopup(options) {
|
|||||||
global.chrome = chrome;
|
global.chrome = chrome;
|
||||||
window.chrome = chrome;
|
window.chrome = chrome;
|
||||||
|
|
||||||
evaluateScript("ui-icons.js");
|
evaluateScript("extension/shared/site-rules.js");
|
||||||
evaluateScript("popup.js");
|
evaluateScript("extension/shared/popup-controls.js");
|
||||||
|
evaluateScript("extension/shared/ui-icons.js");
|
||||||
|
evaluateScript("extension/popup/popup.js");
|
||||||
fireDOMContentLoaded();
|
fireDOMContentLoaded();
|
||||||
|
|
||||||
return chrome;
|
return chrome;
|
||||||
@@ -90,23 +91,24 @@ describe("popup.js", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds sanitized popup buttons and refreshes speed after an action", async () => {
|
it("builds sanitized popup buttons and refreshes speed after an action", async () => {
|
||||||
|
let speedQueryCount = 0;
|
||||||
const chrome = bootPopup({
|
const chrome = bootPopup({
|
||||||
syncData: {
|
syncData: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
controllerButtons: ["faster", "settings", "rewind", "faster"],
|
controllerButtons: ["faster", "settings", "rewind", "faster"],
|
||||||
popupMatchHoverControls: true
|
popupMatchHoverControls: true
|
||||||
}
|
},
|
||||||
});
|
executeScriptImpl: (tabId, details, callback) => {
|
||||||
|
speedQueryCount += 1;
|
||||||
chrome.tabs.executeScript
|
callback(
|
||||||
.mockImplementationOnce((tabId, details, callback) => {
|
speedQueryCount <= 2
|
||||||
callback([
|
? [
|
||||||
{ speed: 1.25, preferred: false },
|
{ speed: 1.25, preferred: false },
|
||||||
{ speed: 1.5, preferred: true }
|
{ speed: 1.5, preferred: true }
|
||||||
]);
|
]
|
||||||
})
|
: [{ speed: 1.75, preferred: true }]
|
||||||
.mockImplementationOnce((tabId, details, callback) => {
|
);
|
||||||
callback([{ speed: 1.75, preferred: true }]);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.tabs.sendMessage.mockImplementation((tabId, message, callback) => {
|
chrome.tabs.sendMessage.mockImplementation((tabId, message, callback) => {
|
||||||
@@ -117,7 +119,6 @@ describe("popup.js", () => {
|
|||||||
callback({ speed: 1.0 });
|
callback({ speed: 1.0 });
|
||||||
});
|
});
|
||||||
|
|
||||||
document.dispatchEvent(new window.Event("DOMContentLoaded"));
|
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
|
|
||||||
const buttons = Array.from(
|
const buttons = Array.from(
|
||||||
@@ -138,8 +139,6 @@ describe("popup.js", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("toggles enablement and closes after a successful refresh", async () => {
|
it("toggles enablement and closes after a successful refresh", async () => {
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
const chrome = bootPopup({
|
const chrome = bootPopup({
|
||||||
syncData: {
|
syncData: {
|
||||||
enabled: false
|
enabled: false
|
||||||
@@ -147,6 +146,7 @@ describe("popup.js", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await flushAsyncWork();
|
await flushAsyncWork();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
expect(document.querySelector("#enable").classList.contains("hide")).toBe(false);
|
expect(document.querySelector("#enable").classList.contains("hide")).toBe(false);
|
||||||
expect(document.querySelector("#disable").classList.contains("hide")).toBe(true);
|
expect(document.querySelector("#disable").classList.contains("hide")).toBe(true);
|
||||||
@@ -158,9 +158,9 @@ describe("popup.js", () => {
|
|||||||
);
|
);
|
||||||
expect(chrome.browserAction.setIcon).toHaveBeenCalledWith({
|
expect(chrome.browserAction.setIcon).toHaveBeenCalledWith({
|
||||||
path: {
|
path: {
|
||||||
19: "icons/icon19.png",
|
19: "assets/icons/icon19.png",
|
||||||
38: "icons/icon38.png",
|
38: "assets/icons/icon38.png",
|
||||||
48: "icons/icon48.png"
|
48: "assets/icons/icon48.png"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import controllerUtils from "../shared/controller-utils.js";
|
import controllerUtils from "../extension/shared/controller-utils.js";
|
||||||
import importExportUtils from "../shared/import-export.js";
|
import importExportUtils from "../extension/shared/import-export.js";
|
||||||
import keyBindingUtils from "../shared/key-bindings.js";
|
import keyBindingUtils from "../extension/shared/key-bindings.js";
|
||||||
import popupControls from "../shared/popup-controls.js";
|
import popupControls from "../extension/shared/popup-controls.js";
|
||||||
import siteRules from "../shared/site-rules.js";
|
import siteRules from "../extension/shared/site-rules.js";
|
||||||
|
|
||||||
describe("shared helpers", () => {
|
describe("shared helpers", () => {
|
||||||
it("matches site rules and skips invalid regex patterns", () => {
|
it("matches site rules and skips invalid regex patterns", () => {
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ module.exports = defineConfig({
|
|||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
globals: true,
|
globals: true,
|
||||||
restoreMocks: true,
|
restoreMocks: true,
|
||||||
include: ["tests/**/*.test.js"],
|
include: ["tests/**/*.test.js", "tests/**/*.spec.js"],
|
||||||
setupFiles: ["./tests/setup.js"]
|
setupFiles: ["./tests/setup.js"]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user