feat: add PWA manifest and service worker routes with coverage

This commit is contained in:
Codex
2026-02-18 12:39:10 +00:00
parent 78d72cc4a9
commit 6ff259c0d0
4 changed files with 55 additions and 0 deletions

View File

@@ -20,6 +20,14 @@ function html(status, markup) {
};
}
function text(status, body, contentType) {
return {
status,
headers: { "content-type": contentType || "text/plain; charset=utf-8" },
body,
};
}
function parseJSON(rawBody) {
if (!rawBody) {
return {};
@@ -112,6 +120,30 @@ function buildApp({ config }) {
return json(200, { ok: true });
}
if (method === "GET" && path === "/manifest.webmanifest") {
return text(
200,
JSON.stringify({
name: "X Article to Audio",
short_name: "XArtAudio",
start_url: "/",
display: "standalone",
background_color: "#1d232a",
theme_color: "#1d232a",
icons: [],
}),
"application/manifest+json; charset=utf-8",
);
}
if (method === "GET" && path === "/sw.js") {
return text(
200,
"self.addEventListener('install', () => self.skipWaiting()); self.addEventListener('activate', () => self.clients.claim());",
"application/javascript; charset=utf-8",
);
}
if (method === "GET" && path === "/") {
return html(200, renderHomePage({
authenticated: Boolean(userId),

View File

@@ -16,12 +16,19 @@ function layout({ title, content }) {
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${escapeHtml(title)}</title>
<meta name="theme-color" content="#1d232a" />
<link rel="manifest" href="/manifest.webmanifest" />
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/dist/full.css" rel="stylesheet" type="text/css" />
</head>
<body class="min-h-screen bg-base-200">
<main class="max-w-md mx-auto p-4 space-y-4">
${content}
</main>
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js").catch(() => {});
}
</script>
</body>
</html>`;
}

View File

@@ -44,6 +44,21 @@ test("rejects invalid X webhook signature", () => {
assert.equal(response.status, 401);
});
test("serves pwa manifest route", () => {
const app = createApp();
const response = app.handleRequest({
method: "GET",
path: "/manifest.webmanifest",
headers: {},
rawBody: "",
});
assert.equal(response.status, 200);
assert.match(response.headers["content-type"], /application\/manifest\+json/);
const body = JSON.parse(response.body);
assert.equal(body.short_name, "XArtAudio");
});
test("X webhook returns not_article response and no charge", () => {
const app = createApp();
postJSON(app, "/api/webhooks/polar", { userId: "u1", credits: 5, eventId: "evt1" }, "polar-secret");

View File

@@ -8,6 +8,7 @@ test("layout includes daisyui stylesheet and mobile-first wrapper", () => {
const html = layout({ title: "t", content: "x" });
assert.match(html, /daisyui@5/);
assert.match(html, /max-w-md mx-auto p-4/);
assert.match(html, /manifest.webmanifest/);
});
test("home page renders jobs list and wallet credits", () => {