feat: add PWA manifest and service worker routes with coverage
This commit is contained in:
32
src/app.js
32
src/app.js
@@ -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),
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user