Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Deploy website to GitHub Pages / Deploy static website (push) Canceled after 0s
The Linux download card now presents only the preview version, DEB format, size, and checksum. Preview index validation and fail-closed download behavior are unchanged. Release note: 官网龙芯下载信息现仅展示版本、包格式、大小与校验值。
317 lines
11 KiB
JavaScript
317 lines
11 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { JSDOM } from "jsdom";
|
|
|
|
const { test } = process.env.VITEST
|
|
? await import("vitest")
|
|
: await import("node:test");
|
|
const source = await readFile(path.resolve("sites/app.js"), "utf8");
|
|
|
|
const createMediaQueries = (window, legacy) => {
|
|
const queries = new Map();
|
|
window.matchMedia = (media) => {
|
|
if (!queries.has(media)) {
|
|
const listeners = new Set();
|
|
const query = {
|
|
media,
|
|
matches: media === "(max-width: 719px)",
|
|
addEventListener: legacy
|
|
? undefined
|
|
: (_type, listener) => listeners.add(listener),
|
|
addListener: legacy
|
|
? (listener) => listeners.add(listener)
|
|
: undefined,
|
|
dispatch(matches) {
|
|
query.matches = matches;
|
|
for (const listener of listeners) {
|
|
listener(query);
|
|
}
|
|
},
|
|
};
|
|
queries.set(media, query);
|
|
}
|
|
return queries.get(media);
|
|
};
|
|
return queries;
|
|
};
|
|
|
|
const renderApp = (legacy = false) => {
|
|
const dom = new JSDOM(
|
|
`<!doctype html>
|
|
<html lang="en">
|
|
<head><meta name="theme-color" content="#f6f8fb"></head>
|
|
<body>
|
|
<a id="skip" href="#main">Skip</a>
|
|
<header data-site-header>
|
|
<button
|
|
type="button"
|
|
aria-label="Open navigation"
|
|
aria-expanded="false"
|
|
data-menu-toggle
|
|
>Menu</button>
|
|
<nav data-navigation>
|
|
<a id="first-nav-link" href="#download">Download</a>
|
|
<a href="#features">Features</a>
|
|
</nav>
|
|
<button type="button" data-theme-toggle>Theme</button>
|
|
</header>
|
|
<div data-menu-backdrop></div>
|
|
<main id="main">
|
|
<section id="download"></section>
|
|
<section id="features"></section>
|
|
</main>
|
|
<footer id="footer">Footer</footer>
|
|
<span data-current-year></span>
|
|
</body>
|
|
</html>`,
|
|
{
|
|
runScripts: "outside-only",
|
|
url: "https://example.test/en.html?lang=en",
|
|
},
|
|
);
|
|
const { window } = dom;
|
|
const inertState = new WeakMap();
|
|
Object.defineProperty(window.HTMLElement.prototype, "inert", {
|
|
configurable: true,
|
|
get() {
|
|
return inertState.get(this) ?? false;
|
|
},
|
|
set(value) {
|
|
inertState.set(this, Boolean(value));
|
|
},
|
|
});
|
|
const queries = createMediaQueries(window, legacy);
|
|
window.eval(source);
|
|
return { dom, queries, window };
|
|
};
|
|
|
|
const renderLoongArchPreview = (payload) => {
|
|
const dom = new JSDOM(
|
|
`<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head><meta name="theme-color" content="#f6f8fb"></head>
|
|
<body>
|
|
<header data-site-header>
|
|
<button type="button" data-menu-toggle>菜单</button>
|
|
<nav data-navigation></nav>
|
|
<button type="button" data-theme-toggle>主题</button>
|
|
</header>
|
|
<div data-menu-backdrop></div>
|
|
<main>
|
|
<article class="download-card">
|
|
<div data-download-card="linux">
|
|
<select data-download-arch>
|
|
<option value="x64">x64</option>
|
|
<option value="arm64">ARM64</option>
|
|
<option value="loong64">龙芯 LoongArch(实验预览)</option>
|
|
</select>
|
|
<select data-download-format>
|
|
<option value="AppImage">AppImage</option>
|
|
<option value="deb">DEB</option>
|
|
<option value="rpm">RPM</option>
|
|
</select>
|
|
</div>
|
|
<a
|
|
class="button button--download"
|
|
href="https://github.com/mesalogo/goodbuddy/releases/latest"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
data-release-link
|
|
>下载 Linux 版<span class="sr-only">(在新窗口打开)</span></a>
|
|
<p data-download-meta></p>
|
|
</article>
|
|
</main>
|
|
<span data-current-year></span>
|
|
</body>
|
|
</html>`,
|
|
{
|
|
runScripts: "outside-only",
|
|
url: "https://example.test/",
|
|
},
|
|
);
|
|
const { window } = dom;
|
|
createMediaQueries(window, false);
|
|
window.TextDecoder = TextDecoder;
|
|
window.GoodBuddyReleaseIndex = {
|
|
maximumIndexBytes: 512 * 1024,
|
|
maximumPreviewIndexBytes: 16 * 1024,
|
|
validateReleaseIndex() {
|
|
throw new Error("正式发布索引在此测试中不可用");
|
|
},
|
|
validateLoongArchPreviewIndex(value) {
|
|
return value;
|
|
},
|
|
};
|
|
window.fetch = async (url) => {
|
|
if (!String(url).includes("/loongarch-preview/")) {
|
|
throw new Error("正式发布索引在此测试中不可用");
|
|
}
|
|
if (!payload) {
|
|
throw new Error("龙芯预览索引不可用");
|
|
}
|
|
const bytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
let delivered = false;
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
headers: {
|
|
get(name) {
|
|
return name === "content-length" ? String(bytes.length) : null;
|
|
},
|
|
},
|
|
body: {
|
|
getReader() {
|
|
return {
|
|
async read() {
|
|
if (delivered) {
|
|
return { done: true, value: undefined };
|
|
}
|
|
delivered = true;
|
|
return { done: false, value: bytes };
|
|
},
|
|
};
|
|
},
|
|
},
|
|
};
|
|
};
|
|
window.eval(source);
|
|
return { dom, window };
|
|
};
|
|
|
|
for (const legacy of [false, true]) {
|
|
test(
|
|
`mobile menu isolates content and restores focus with ${
|
|
legacy ? "legacy" : "modern"
|
|
} media listeners`,
|
|
async () => {
|
|
const { dom, queries, window } = renderApp(legacy);
|
|
const header = window.document.querySelector("[data-site-header]");
|
|
const toggle = window.document.querySelector("[data-menu-toggle]");
|
|
const firstLink = window.document.querySelector("#first-nav-link");
|
|
const main = window.document.querySelector("main");
|
|
const footer = window.document.querySelector("footer");
|
|
const skip = window.document.querySelector("#skip");
|
|
const backdrop = window.document.querySelector("[data-menu-backdrop]");
|
|
|
|
main.inert = true;
|
|
toggle.click();
|
|
assert.equal(header.classList.contains("is-menu-open"), true);
|
|
assert.equal(toggle.getAttribute("aria-expanded"), "true");
|
|
assert.equal(window.document.activeElement, firstLink);
|
|
assert.equal(skip.inert, true);
|
|
assert.equal(main.inert, true);
|
|
assert.equal(footer.inert, true);
|
|
assert.equal(backdrop.inert, false);
|
|
assert.equal(backdrop.classList.contains("is-active"), true);
|
|
|
|
window.document.dispatchEvent(
|
|
new window.KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
|
|
);
|
|
assert.equal(header.classList.contains("is-menu-open"), false);
|
|
assert.equal(toggle.getAttribute("aria-expanded"), "false");
|
|
assert.equal(window.document.activeElement, toggle);
|
|
assert.equal(skip.inert, false);
|
|
assert.equal(main.inert, true);
|
|
assert.equal(footer.inert, false);
|
|
assert.equal(backdrop.classList.contains("is-active"), false);
|
|
|
|
toggle.click();
|
|
backdrop.click();
|
|
assert.equal(header.classList.contains("is-menu-open"), false);
|
|
assert.equal(window.document.activeElement, toggle);
|
|
|
|
toggle.click();
|
|
footer.click();
|
|
assert.equal(header.classList.contains("is-menu-open"), false);
|
|
assert.equal(window.document.activeElement, toggle);
|
|
|
|
toggle.click();
|
|
firstLink.click();
|
|
await new Promise((resolve) => window.setTimeout(resolve, 0));
|
|
assert.equal(header.classList.contains("is-menu-open"), false);
|
|
assert.equal(window.document.activeElement, toggle);
|
|
|
|
toggle.click();
|
|
queries.get("(max-width: 719px)").dispatch(false);
|
|
assert.equal(header.classList.contains("is-menu-open"), false);
|
|
assert.equal(toggle.getAttribute("aria-expanded"), "false");
|
|
assert.equal(footer.inert, false);
|
|
|
|
dom.window.close();
|
|
},
|
|
);
|
|
}
|
|
|
|
test("enables only a validated LoongArch preview download", async () => {
|
|
const artifactUrl =
|
|
"https://goodbuddy.oss-cn-beijing.aliyuncs.com/releases/loongarch-preview/v0.11.5/GoodBuddy-0.11.5-linux-loong64-preview.deb";
|
|
const { dom, window } = renderLoongArchPreview({
|
|
goodBuddyVersion: "0.11.5",
|
|
artifact: {
|
|
size: 186_853_872,
|
|
sha256: "a".repeat(64),
|
|
url: artifactUrl,
|
|
},
|
|
});
|
|
await new Promise((resolve) => window.setTimeout(resolve, 0));
|
|
|
|
const arch = window.document.querySelector("[data-download-arch]");
|
|
const format = window.document.querySelector(
|
|
"[data-download-format]",
|
|
);
|
|
const link = window.document.querySelector("[data-release-link]");
|
|
const meta = window.document.querySelector("[data-download-meta]");
|
|
arch.value = "loong64";
|
|
arch.dispatchEvent(new window.Event("change"));
|
|
|
|
assert.equal(format.value, "deb");
|
|
assert.equal(format.disabled, true);
|
|
assert.equal(format.options[0].hidden, true);
|
|
assert.equal(format.options[1].hidden, false);
|
|
assert.equal(format.options[2].hidden, true);
|
|
assert.equal(link.getAttribute("href"), artifactUrl);
|
|
assert.equal(link.getAttribute("target"), "_blank");
|
|
assert.equal(link.getAttribute("rel"), "noreferrer");
|
|
assert.equal(link.hasAttribute("aria-disabled"), false);
|
|
assert.equal(link.classList.contains("is-disabled"), false);
|
|
assert.equal(
|
|
meta.textContent,
|
|
"GoodBuddy 0.11.5 · 178 MB · 实验预览 · SHA-256 aaaaaaaaaaaa…",
|
|
);
|
|
dom.window.close();
|
|
});
|
|
|
|
test("keeps the LoongArch preview disabled when its index is unavailable", async () => {
|
|
const { dom, window } = renderLoongArchPreview(null);
|
|
await new Promise((resolve) => window.setTimeout(resolve, 0));
|
|
|
|
const arch = window.document.querySelector("[data-download-arch]");
|
|
const format = window.document.querySelector(
|
|
"[data-download-format]",
|
|
);
|
|
const link = window.document.querySelector("[data-release-link]");
|
|
const meta = window.document.querySelector("[data-download-meta]");
|
|
arch.value = "loong64";
|
|
arch.dispatchEvent(new window.Event("change"));
|
|
|
|
assert.equal(format.value, "deb");
|
|
assert.equal(format.disabled, true);
|
|
assert.equal(link.hasAttribute("href"), false);
|
|
assert.equal(link.getAttribute("aria-disabled"), "true");
|
|
assert.equal(link.tabIndex, -1);
|
|
assert.equal(link.classList.contains("is-disabled"), true);
|
|
assert.equal(
|
|
meta.textContent,
|
|
"实验预览 · 仅 DEB · 索引尚未发布或校验失败。",
|
|
);
|
|
|
|
arch.value = "x64";
|
|
arch.dispatchEvent(new window.Event("change"));
|
|
assert.equal(format.disabled, false);
|
|
assert.equal(format.options[0].hidden, false);
|
|
assert.equal(link.getAttribute("href"), "https://github.com/mesalogo/goodbuddy/releases/latest");
|
|
assert.equal(link.hasAttribute("aria-disabled"), false);
|
|
dom.window.close();
|
|
});
|