Skip to content

Commit 9fbd4d3

Browse files
author
Lukas Geiger
committed
fix: alarm scheduling fuer manual-only feeds + Test-Coverage
- sw.js: Korrekte Alarm-Updates auch bei deaktiviertem Global-Intervall - tests/sw-schedule.test.mjs: Regression-Coverage fuer Scheduling - CHANGELOG/README: aktualisiert
1 parent 5ebfc46 commit 9fbd4d3

4 files changed

Lines changed: 107 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
### Added
66
- Added automated light/dark theme coverage for popup and options CSS variables.
77
- Added a read-only GitHub Actions CI workflow for the Node test suite.
8+
- Added regression coverage for service-worker alarm scheduling.
9+
10+
### Fixed
11+
- Fixed alarm updates for manual-only feeds when global interval is disabled but other feeds define per-feed intervals.
812

913
### Verified
10-
- `npm test` now covers 25 dependency-free Node tests, including theme coverage.
14+
- `npm test` now covers 26 dependency-free Node tests, including theme and service-worker scheduling coverage.
1115

1216
## [1.1.2] — 2026-04-30
1317

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Store listing is planned after the remaining browser and screenshot checks.
5959

6060
## Development
6161

62-
RSS-BOOK has no build step. The repository includes 25 dependency-free Node tests for parser behavior, OPML, storage, bookmark cleanup, feed discovery, folder export, store assets, and light/dark theme CSS coverage:
62+
RSS-BOOK has no build step. The repository includes 26 dependency-free Node tests for parser behavior, OPML, storage, bookmark cleanup, feed discovery, folder export, store assets, service-worker scheduling, and light/dark theme CSS coverage:
6363

6464
```bash
6565
npm test

sw.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,11 @@ async function ensureAlarm() {
7171

7272
async function runUpdateCycle(reason) {
7373
console.log(`[RSS-BOOK] Update cycle (${reason})`);
74+
const { settings } = await getState();
7475
const feeds = await getEnabledFeeds();
7576

7677
for (const feed of feeds) {
77-
if (reason === "alarm" && feed.intervalMinutes > 0) {
78-
const due = !feed.lastFetch || (Date.now() - feed.lastFetch) >= feed.intervalMinutes * 60_000;
79-
if (!due) continue;
80-
}
78+
if (!shouldUpdateFeedForReason(feed, reason, settings)) continue;
8179
try {
8280
await updateOneFeed(feed.id);
8381
} catch (err) {
@@ -97,6 +95,18 @@ async function runUpdateCycle(reason) {
9795
}
9896
}
9997

98+
export function shouldUpdateFeedForReason(feed, reason, settings = {}, now = Date.now()) {
99+
if (reason !== "alarm") return true;
100+
101+
const feedInterval = Number(feed.intervalMinutes) || 0;
102+
if (feedInterval > 0) {
103+
return !feed.lastFetch || (now - feed.lastFetch) >= feedInterval * 60_000;
104+
}
105+
106+
const globalInterval = Number(settings?.globalIntervalMinutes) || 0;
107+
return globalInterval > 0;
108+
}
109+
100110
async function updateOneFeed(feedId) {
101111
const { feeds } = await getState();
102112
const feed = feeds?.[feedId];

tests/sw-schedule.test.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { afterEach, test } from "node:test";
2+
import assert from "node:assert/strict";
3+
4+
afterEach(() => {
5+
delete globalThis.chrome;
6+
});
7+
8+
function installChromeMock() {
9+
const addListener = () => {};
10+
11+
globalThis.chrome = {
12+
runtime: {
13+
onInstalled: { addListener },
14+
onStartup: { addListener },
15+
onMessage: { addListener }
16+
},
17+
alarms: {
18+
onAlarm: { addListener },
19+
async get() {
20+
return null;
21+
},
22+
async clear() {},
23+
async create() {}
24+
},
25+
storage: {
26+
onChanged: { addListener },
27+
local: {
28+
async get() {
29+
return { settings: {}, feeds: {} };
30+
},
31+
async set() {}
32+
}
33+
}
34+
};
35+
}
36+
37+
test("alarm scheduling skips manual-only feeds when global interval is disabled", async () => {
38+
installChromeMock();
39+
const { shouldUpdateFeedForReason } = await import(`../sw.js?sw-schedule=${Date.now()}`);
40+
const now = Date.parse("2026-05-01T12:00:00Z");
41+
42+
assert.equal(
43+
shouldUpdateFeedForReason(
44+
{ intervalMinutes: 0, lastFetch: 0 },
45+
"alarm",
46+
{ globalIntervalMinutes: 0 },
47+
now
48+
),
49+
false
50+
);
51+
assert.equal(
52+
shouldUpdateFeedForReason(
53+
{ intervalMinutes: 0, lastFetch: 0 },
54+
"alarm",
55+
{ globalIntervalMinutes: 15 },
56+
now
57+
),
58+
true
59+
);
60+
assert.equal(
61+
shouldUpdateFeedForReason(
62+
{ intervalMinutes: 30, lastFetch: now - 29 * 60_000 },
63+
"alarm",
64+
{ globalIntervalMinutes: 0 },
65+
now
66+
),
67+
false
68+
);
69+
assert.equal(
70+
shouldUpdateFeedForReason(
71+
{ intervalMinutes: 30, lastFetch: now - 31 * 60_000 },
72+
"alarm",
73+
{ globalIntervalMinutes: 0 },
74+
now
75+
),
76+
true
77+
);
78+
assert.equal(
79+
shouldUpdateFeedForReason(
80+
{ intervalMinutes: 0, lastFetch: 0 },
81+
"manual",
82+
{ globalIntervalMinutes: 0 },
83+
now
84+
),
85+
true
86+
);
87+
});

0 commit comments

Comments
 (0)