source: Klonkt/test/messages-conversations.test.js@ 0448e34

main
Last change on this file since 0448e34 was 0448e34, checked in by Claude (agent) <aiclaude@…>, 5 weeks ago

Gesprekken verborg regels die geen draad werden

Robins melding: een draad die je zelf begonnen bent viel weg onder Gesprekken.
Oorzaak was een halve opruiming van mijn kant. De chips gingen van zes naar
vier, maar msg-item bleef 'sent' en 'msgs' als data-kind uitdelen aan regels die
om wat voor reden dan ook geen draad werden. Die twee chips bestaan niet meer,
dus zo'n regel matchte geen enkel filter en was alleen nog onder Alles te zien.

Geraakt werden: een verzonden bericht zonder post en zonder herkenbare
ontvanger, een mention zonder afzender, en een losse privé-reply. Alles wat een
gesprek is krijgt nu data-kind 'conv'; alleen Activiteit en Moderatie houden een
eigen emmer.

Daarnaast de oorzaak een stap eerder aangepakt: een direct bericht kan zonder
to_handle in ap_outbox staan, en dan vond threadKey geen tegenpartij. De eerste
uit to_actors telt nu ook, met to_handle als winnaar zodat dezelfde persoon niet
twee draden oplevert. Juist een gesprek dat JIJ begon heeft vaak niets anders om
aan te hangen, dus daar deed het ontbreken van die terugval het meeste pijn.

Getest tegen dev: de drie gevallen renderden aantoonbaar als data-kind sent/msgs
en doen dat nu als conv, terwijl like en report op act en mod blijven. Suite
410/410, met twee nieuwe tests op de to_actors-terugval.

Co-Authored-By: Claude Opus 5 <noreply@…>

  • Property mode set to 100644
File size: 6.6 KB
Line 
1// Berichten als gesprekken: antwoorden, mentions en je eigen verzonden
2// berichten vouwen samen tot draden, de rest van de stroom blijft ongemoeid.
3// Dit dekt de groepeerlogica zelf (zuiver, geen DB, geen netwerk); het
4// samenstellen van de stroom zit in getMessages en leunt op de database.
5//
6// Run: npm test
7
8import { test } from 'node:test';
9import assert from 'node:assert/strict';
10
11process.env.DATABASE_PATH = ':memory:';
12process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
13
14const dbMod = await import('../src/config/database.js');
15dbMod.initializeDatabase();
16const AP = await import('../src/services/ActivityPubService.js');
17
18const { threadKey, groupConversations } = AP;
19
20// Aflopend gesorteerd, zoals getMessages ze aanlevert.
21const ts = (n) => new Date(Date.UTC(2026, 0, 1, 12, n)).toISOString();
22
23test('threadKey: een post wint van een persoon', () => {
24 assert.equal(threadKey({ type: 'reply', post_slug: 'hallo', handle: '@anna@a.test' }), 'post:hallo');
25 assert.equal(threadKey({ type: 'sent', post_slug: 'hallo', to_handle: '@anna@a.test' }), 'post:hallo');
26 // Zonder post loopt de draad per tegenpartij, hoofdletter- en @-ongevoelig.
27 assert.equal(threadKey({ type: 'mention', handle: '@Anna@A.test' }), 'actor:anna@a.test');
28 assert.equal(threadKey({ type: 'sent', to_handle: 'Anna@a.test' }), 'actor:anna@a.test');
29});
30
31test('threadKey: alles wat geen gesprek is krijgt geen draad', () => {
32 for (const type of ['like', 'announce', 'follow', 'report', 'poll_done']) {
33 assert.equal(threadKey({ type, post_slug: 'hallo' }), null, `${type} hoort geen draad te krijgen`);
34 }
35 assert.equal(threadKey(null), null);
36 assert.equal(threadKey({ type: 'mention' }), null, 'een mention zonder afzender heeft geen sleutel');
37});
38
39test('ontvangen en verzonden op dezelfde post komen in EEN draad', () => {
40 const out = groupConversations([
41 { type: 'sent', post_slug: 'hallo', content: 'graag gedaan', created_at: ts(3) },
42 { type: 'reply', post_slug: 'hallo', post_title: 'Hallo fediverse', handle: '@anna@a.test', name: 'Anna', content: 'dank!', created_at: ts(2) },
43 { type: 'reply', post_slug: 'hallo', post_title: 'Hallo fediverse', handle: '@bo@b.test', name: 'Bo', content: 'mooi', created_at: ts(1) },
44 ]);
45 assert.equal(out.length, 1, 'drie berichten, één draad');
46 const t = out[0];
47 assert.equal(t.type, 'thread');
48 assert.equal(t.count, 3);
49 // De context hoort bij de draad: dit gesprek gaat over een post.
50 assert.deepEqual(t.post, { slug: 'hallo', title: 'Hallo fediverse' });
51 // Een gesprek leest naar beneden: oud → nieuw.
52 assert.deepEqual(t.messages.map((m) => m.content), ['mooi', 'dank!', 'graag gedaan']);
53 // De draad staat op de tijd van zijn NIEUWSTE bericht, zodat een levend
54 // gesprek bovenaan komt.
55 assert.equal(t.created_at, ts(3));
56 // Jij zit er niet bij als deelnemer, de anderen wel.
57 assert.deepEqual(t.people.map((p) => p.name), ['Bo', 'Anna']);
58 assert.equal(t.mine, true);
59});
60
61test('een mention zonder post loopt per persoon, niet per post', () => {
62 const out = groupConversations([
63 { type: 'sent', to_handle: '@cas@c.test', content: 'hoi terug', created_at: ts(2) },
64 { type: 'mention', handle: '@cas@c.test', name: 'Cas', content: 'hoi!', created_at: ts(1) },
65 ]);
66 assert.equal(out.length, 1);
67 assert.equal(out[0].key, 'actor:cas@c.test');
68 assert.equal(out[0].post, null, 'zonder post is er geen context-link');
69 assert.deepEqual(out[0].messages.map((m) => m.content), ['hoi!', 'hoi terug']);
70});
71
72test('twee posts blijven twee draden, op volgorde van hun nieuwste bericht', () => {
73 const out = groupConversations([
74 { type: 'reply', post_slug: 'twee', post_title: 'Twee', handle: '@anna@a.test', created_at: ts(4) },
75 { type: 'reply', post_slug: 'een', post_title: 'Een', handle: '@bo@b.test', created_at: ts(3) },
76 { type: 'reply', post_slug: 'twee', post_title: 'Twee', handle: '@bo@b.test', created_at: ts(2) },
77 ]);
78 assert.deepEqual(out.map((t) => t.key), ['post:twee', 'post:een']);
79 assert.equal(out[0].count, 2);
80 assert.equal(out[1].count, 1);
81});
82
83test('likes, boosts en follows stromen ongemoeid tussen de draden door', () => {
84 const out = groupConversations([
85 { type: 'like', post_slug: 'hallo', handle: '@anna@a.test', created_at: ts(4) },
86 { type: 'reply', post_slug: 'hallo', post_title: 'Hallo', handle: '@bo@b.test', created_at: ts(3) },
87 { type: 'follow', handle: '@cas@c.test', created_at: ts(2) },
88 { type: 'report', handle: '@dee@d.test', created_at: ts(1) },
89 ]);
90 assert.deepEqual(out.map((i) => i.type), ['like', 'thread', 'follow', 'report']);
91 // Een like op dezelfde post trekt die post NIET de draad in: het is geen
92 // gesprek, en Activiteit blijft zijn eigen chip houden.
93 assert.equal(out[0].post_slug, 'hallo');
94});
95
96test('de titel komt van welk bericht in de draad hem ook maar kent', () => {
97 // Een verzonden antwoord kent alleen de slug; een ontvangen antwoord de titel.
98 const out = groupConversations([
99 { type: 'sent', post_slug: 'hallo', created_at: ts(2) },
100 { type: 'reply', post_slug: 'hallo', post_title: 'Hallo fediverse', handle: '@anna@a.test', created_at: ts(1) },
101 ]);
102 assert.deepEqual(out[0].post, { slug: 'hallo', title: 'Hallo fediverse' });
103});
104
105test('lege en rommelige invoer levert geen kapotte draden op', () => {
106 assert.deepEqual(groupConversations([]), []);
107 assert.deepEqual(groupConversations(null), []);
108 // Een gesprekssoort zonder sleutel blijft een losse regel in plaats van te
109 // verdwijnen: nooit een bericht kwijtraken in de groepering.
110 const out = groupConversations([{ type: 'sent', content: 'aan niemand', created_at: ts(1) }]);
111 assert.equal(out.length, 1);
112 assert.equal(out[0].type, 'sent');
113});
114
115test('een direct bericht zonder to_handle valt terug op to_actors', () => {
116 // Precies het geval waarin het het onlogischst is dat er geen draad ontstaat:
117 // een gesprek dat JIJ begon. Zonder deze terugval bleef het een losse regel.
118 const key = threadKey({
119 type: 'sent',
120 to_actors: JSON.stringify(['https://a.test/users/anna', 'https://b.test/users/bo']),
121 created_at: ts(1),
122 });
123 assert.equal(key, 'actor:anna@a.test');
124 // Rommel in de kolom mag niets omgooien.
125 assert.equal(threadKey({ type: 'sent', to_actors: 'geen json' }), null);
126 assert.equal(threadKey({ type: 'sent', to_actors: '[]' }), null);
127});
128
129test('to_handle wint van to_actors, zodat een draad niet splitst', () => {
130 const a = threadKey({ type: 'sent', to_handle: '@anna@a.test' });
131 const b = threadKey({ type: 'sent', to_handle: '@anna@a.test', to_actors: JSON.stringify(['https://b.test/users/bo']) });
132 assert.equal(a, b, 'dezelfde tegenpartij hoort dezelfde sleutel te geven');
133});
Note: See TracBrowser for help on using the repository browser.