| 1 | // De badge: ongelezen per gesprek (shaer-frontend-3tx).
|
|---|
| 2 | //
|
|---|
| 3 | // Twee dingen liggen hier vast. Een COUNT en geen bijgehouden getal -- bij een
|
|---|
| 4 | // verwijdering klopt het dan vanzelf weer, waar een teller zou blijven staan.
|
|---|
| 5 | // En Read als GEBEURTENIS: markeren is optellend, dus een toestel dat een week
|
|---|
| 6 | // uit stond kan je gelezen berichten niet terugzetten op ongelezen.
|
|---|
| 7 | //
|
|---|
| 8 | // In-memory SQLite. Run: npm test
|
|---|
| 9 | import { test } from 'node:test';
|
|---|
| 10 | import assert from 'node:assert/strict';
|
|---|
| 11 |
|
|---|
| 12 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 13 | process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
|
|---|
| 14 |
|
|---|
| 15 | const dbMod = await import('../src/config/database.js');
|
|---|
| 16 | const db = dbMod.default;
|
|---|
| 17 | dbMod.initializeDatabase();
|
|---|
| 18 | const AP = await import('../src/services/ActivityPubService.js');
|
|---|
| 19 |
|
|---|
| 20 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 21 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 22 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'kind', 'Kind', 'u1');
|
|---|
| 23 |
|
|---|
| 24 | const OMA = 'https://elders/u/oma';
|
|---|
| 25 | const OOM = 'https://elders/u/oom';
|
|---|
| 26 | const arrived = (uri, from, stamp, wave = 0) => db.prepare(
|
|---|
| 27 | `INSERT INTO ap_mentions (slug, object_uri, actor_uri, actor_name, content, published, wave)
|
|---|
| 28 | VALUES ('kind', ?, ?, 'iemand', '<p>hoi</p>', ?, ?)`).run(uri, from, stamp, wave);
|
|---|
| 29 |
|
|---|
| 30 | arrived('https://elders/n/1', OMA, '2026-08-01T10:00:00Z');
|
|---|
| 31 | arrived('https://elders/n/2', OMA, '2026-08-01T11:00:00Z');
|
|---|
| 32 | arrived('https://elders/n/3', OMA, '2026-08-01T12:00:00Z');
|
|---|
| 33 | arrived('https://elders/n/w', OOM, '2026-08-01T09:00:00Z', 1);
|
|---|
| 34 |
|
|---|
| 35 | test('alles is ongelezen tot je iets gelezen hebt', () => {
|
|---|
| 36 | const u = AP.unreadPerConversation('kind');
|
|---|
| 37 | assert.equal(u.get(OMA).n, 3);
|
|---|
| 38 | assert.equal(u.get(OOM).n, 1);
|
|---|
| 39 | // Een zwaai telt apart: dat is een zetje van een guardian, geen gesprek.
|
|---|
| 40 | assert.equal(u.get(OOM).wave, true);
|
|---|
| 41 | assert.equal(u.get(OMA).wave, false);
|
|---|
| 42 | });
|
|---|
| 43 |
|
|---|
| 44 | test('lezen tot het tweede bericht laat er een over', () => {
|
|---|
| 45 | AP.markRead('kind', 'https://elders/n/2');
|
|---|
| 46 | assert.equal(AP.unreadPerConversation('kind').get(OMA).n, 1);
|
|---|
| 47 | assert.equal(AP.unreadPerConversation('kind').get(OOM).n, 1, 'een ander gesprek raakt het niet');
|
|---|
| 48 | });
|
|---|
| 49 |
|
|---|
| 50 | test('een oud toestel kan gelezen berichten niet terugzetten', () => {
|
|---|
| 51 | // Precies het geval waar Read-als-gebeurtenis voor is: markeren is
|
|---|
| 52 | // optellend, dus dit doet niets.
|
|---|
| 53 | AP.markRead('kind', 'https://elders/n/1');
|
|---|
| 54 | assert.equal(AP.unreadPerConversation('kind').get(OMA).n, 1, 'nog steeds een');
|
|---|
| 55 | });
|
|---|
| 56 |
|
|---|
| 57 | test('alles gelezen is geen rij, niet een nul', () => {
|
|---|
| 58 | AP.markRead('kind', 'https://elders/n/3');
|
|---|
| 59 | assert.equal(AP.unreadPerConversation('kind').get(OMA), undefined);
|
|---|
| 60 | });
|
|---|
| 61 |
|
|---|
| 62 | test('een verwijdering laat het getal vanzelf kloppen', () => {
|
|---|
| 63 | // Waar een bijgehouden teller zou blijven staan: badge zegt 1, er is niets.
|
|---|
| 64 | arrived('https://elders/n/4', OMA, '2026-08-01T13:00:00Z');
|
|---|
| 65 | assert.equal(AP.unreadPerConversation('kind').get(OMA).n, 1);
|
|---|
| 66 | db.prepare("DELETE FROM ap_mentions WHERE object_uri = ?").run('https://elders/n/4');
|
|---|
| 67 | assert.equal(AP.unreadPerConversation('kind').get(OMA), undefined);
|
|---|
| 68 | });
|
|---|
| 69 |
|
|---|
| 70 | test('een onbekende note markeert niets, en dat is geen fout', () => {
|
|---|
| 71 | assert.equal(AP.markRead('kind', 'https://elders/n/bestaat-niet'), null);
|
|---|
| 72 | });
|
|---|
| 73 |
|
|---|
| 74 | test('een dichte messages-poort telt niet mee wat hij verbergt', () => {
|
|---|
| 75 | // Anders vertelt het getal precies wat de poort verbergt: 'er zijn 3
|
|---|
| 76 | // berichten' terwijl de app er geen laat zien.
|
|---|
| 77 | const OPA = 'https://elders/u/opa';
|
|---|
| 78 | arrived('https://elders/n/opa1', OPA, '2026-08-02T10:00:00Z');
|
|---|
| 79 | arrived('https://elders/n/oom2', OOM, '2026-08-02T11:00:00Z');
|
|---|
| 80 | db.prepare(`INSERT INTO ap_mentions (slug, object_uri, actor_uri, actor_name, content, published, help_request)
|
|---|
| 81 | VALUES ('kind', 'https://elders/n/boei', ?, 'iemand', '<p>help</p>', '2026-08-02T12:00:00Z', 1)`).run(OPA);
|
|---|
| 82 |
|
|---|
| 83 | const dicht = AP.unreadPerConversation('kind', { messagesAllowed: false, guardians: new Set([OOM]) });
|
|---|
| 84 | assert.equal(dicht.get(OPA).n, 1, 'alleen de boei van opa telt');
|
|---|
| 85 | assert.ok(dicht.get(OOM), 'het guardian-kanaal gaat altijd door');
|
|---|
| 86 |
|
|---|
| 87 | const open = AP.unreadPerConversation('kind', { messagesAllowed: true });
|
|---|
| 88 | assert.equal(open.get(OPA).n, 2, 'met de poort open tellen ze allebei');
|
|---|
| 89 | });
|
|---|