integration tests for local echo of toggling reactions
This commit is contained in:
parent
4d19f8d21d
commit
18562d30d8
1 changed files with 186 additions and 0 deletions
|
@ -190,6 +190,192 @@ class ReactionViewModel {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// matrix classes uses in the integration test below
|
||||
import {User} from "../../../../matrix/User.js";
|
||||
import {SendQueue} from "../../../../matrix/room/sending/SendQueue.js";
|
||||
import {Timeline} from "../../../../matrix/room/timeline/Timeline.js";
|
||||
import {EventEntry} from "../../../../matrix/room/timeline/entries/EventEntry.js";
|
||||
import {RelationWriter} from "../../../../matrix/room/timeline/persistence/RelationWriter.js";
|
||||
import {FragmentIdComparer} from "../../../../matrix/room/timeline/FragmentIdComparer.js";
|
||||
import {createAnnotation} from "../../../../matrix/room/timeline/relations.js";
|
||||
// mocks
|
||||
import {Clock as MockClock} from "../../../../mocks/Clock.js";
|
||||
import {createMockStorage} from "../../../../mocks/Storage.js";
|
||||
import {ListObserver} from "../../../../mocks/ListObserver.js";
|
||||
import {createEvent, withTextBody, withContent} from "../../../../mocks/event.js";
|
||||
import {NullLogItem, NullLogger} from "../../../../logging/NullLogger.js";
|
||||
import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js";
|
||||
// other imports
|
||||
import {BaseMessageTile} from "./tiles/BaseMessageTile.js";
|
||||
import {MappedList} from "../../../../observable/list/MappedList.js";
|
||||
|
||||
export function tests() {
|
||||
const fragmentIdComparer = new FragmentIdComparer([]);
|
||||
const roomId = "$abc";
|
||||
const alice = "@alice:hs.tld";
|
||||
const bob = "@bob:hs.tld";
|
||||
const logger = new NullLogger();
|
||||
|
||||
function findInIterarable(it, predicate) {
|
||||
let i = 0;
|
||||
for (const item of it) {
|
||||
if (predicate(item, i)) {
|
||||
return item;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
throw new Error("not found");
|
||||
}
|
||||
|
||||
function mapMessageEntriesToBaseMessageTile(timeline, queue) {
|
||||
const room = {
|
||||
id: roomId,
|
||||
sendEvent(eventType, content, attachments, log) {
|
||||
return queue.enqueueEvent(eventType, content, attachments, log);
|
||||
},
|
||||
sendRedaction(eventIdOrTxnId, reason, log) {
|
||||
return queue.enqueueRedaction(eventIdOrTxnId, reason, log);
|
||||
}
|
||||
};
|
||||
const tiles = new MappedList(timeline.entries, entry => {
|
||||
if (entry.eventType === "m.room.message") {
|
||||
return new BaseMessageTile({entry, room, timeline, platform: {logger}});
|
||||
}
|
||||
return null;
|
||||
}, (tile, params, entry) => tile?.updateEntry(entry, params));
|
||||
return tiles;
|
||||
}
|
||||
|
||||
// these are more an integration test than unit tests, but fully tests the local echo when toggling
|
||||
return {
|
||||
"toggling reaction with own remote reaction": async assert => {
|
||||
// 1. put message and reaction in storage
|
||||
const messageEvent = withTextBody("Dogs > Cats", createEvent("m.room.message", "!abc", bob));
|
||||
const myReactionEvent = withContent(createAnnotation(messageEvent.event_id, "🐶"), createEvent("m.reaction", "!def", alice));
|
||||
myReactionEvent.origin_server_ts = 5;
|
||||
const myReactionEntry = new EventEntry({event: myReactionEvent, roomId}, fragmentIdComparer);
|
||||
const relationWriter = new RelationWriter({roomId, ownUserId: alice, fragmentIdComparer});
|
||||
const storage = await createMockStorage();
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.timelineEvents,
|
||||
storage.storeNames.timelineRelations,
|
||||
storage.storeNames.timelineFragments
|
||||
]);
|
||||
txn.timelineFragments.add({id: 1, roomId});
|
||||
txn.timelineEvents.insert({fragmentId: 1, eventIndex: 2, event: messageEvent, roomId});
|
||||
txn.timelineEvents.insert({fragmentId: 1, eventIndex: 3, event: myReactionEvent, roomId});
|
||||
await relationWriter.writeRelation(myReactionEntry, txn, new NullLogItem());
|
||||
await txn.complete();
|
||||
// 2. setup queue & timeline
|
||||
const queue = new SendQueue({roomId, storage, hsApi: new MockHomeServer().api});
|
||||
const timeline = new Timeline({roomId, storage, fragmentIdComparer,
|
||||
clock: new MockClock(), pendingEvents: queue.pendingEvents});
|
||||
// 3. load the timeline, which will load the message with the reaction
|
||||
await timeline.load(new User(alice), "join", new NullLogItem());
|
||||
const tiles = mapMessageEntriesToBaseMessageTile(timeline, queue);
|
||||
// 4. subscribe to the queue to observe, and the tiles (so we can safely iterate)
|
||||
const queueObserver = new ListObserver();
|
||||
queue.pendingEvents.subscribe(queueObserver);
|
||||
tiles.subscribe(new ListObserver());
|
||||
const messageTile = findInIterarable(tiles, e => !!e); // the other entries are mapped to null
|
||||
const reactionVM = messageTile.reactions.getReactionViewModelForKey("🐶");
|
||||
// 5. test toggling
|
||||
// make sure the preexisting reaction is counted
|
||||
assert.equal(reactionVM.count, 1);
|
||||
// 5.1. unset reaction, should redact the pre-existing reaction
|
||||
await reactionVM.toggleReaction();
|
||||
{
|
||||
assert.equal(reactionVM.count, 0);
|
||||
const {value: redaction, type} = await queueObserver.next();
|
||||
assert.equal("add", type);
|
||||
assert.equal(redaction.eventType, "m.room.redaction");
|
||||
assert.equal(redaction.relatedEventId, myReactionEntry.id);
|
||||
// SendQueue puts redaction in sending status, as it is first in the queue
|
||||
assert.equal("update", (await queueObserver.next()).type);
|
||||
}
|
||||
// 5.2. set reaction, should send a new reaction as the redaction is already sending
|
||||
await reactionVM.toggleReaction();
|
||||
let reactionIndex;
|
||||
{
|
||||
assert.equal(reactionVM.count, 1);
|
||||
const {value: reaction, type, index} = await queueObserver.next();
|
||||
assert.equal("add", type);
|
||||
assert.equal(reaction.eventType, "m.reaction");
|
||||
assert.equal(reaction.relatedEventId, messageEvent.event_id);
|
||||
reactionIndex = index;
|
||||
}
|
||||
// 5.3. unset reaction, should abort the previous pending reaction as it hasn't started sending yet
|
||||
await reactionVM.toggleReaction();
|
||||
{
|
||||
assert.equal(reactionVM.count, 0);
|
||||
const {index, type} = await queueObserver.next();
|
||||
assert.equal("remove", type);
|
||||
assert.equal(reactionIndex, index);
|
||||
}
|
||||
},
|
||||
"toggling reaction without own remote reaction": async assert => {
|
||||
// 1. put message in storage
|
||||
const messageEvent = withTextBody("Dogs > Cats", createEvent("m.room.message", "!abc", bob));
|
||||
const storage = await createMockStorage();
|
||||
|
||||
const txn = await storage.readWriteTxn([
|
||||
storage.storeNames.timelineEvents,
|
||||
storage.storeNames.timelineFragments
|
||||
]);
|
||||
txn.timelineFragments.add({id: 1, roomId});
|
||||
txn.timelineEvents.insert({fragmentId: 1, eventIndex: 2, event: messageEvent, roomId});
|
||||
await txn.complete();
|
||||
// 2. setup queue & timeline
|
||||
const queue = new SendQueue({roomId, storage, hsApi: new MockHomeServer().api});
|
||||
const timeline = new Timeline({roomId, storage, fragmentIdComparer,
|
||||
clock: new MockClock(), pendingEvents: queue.pendingEvents});
|
||||
|
||||
// 3. load the timeline, which will load the message with the reaction
|
||||
await timeline.load(new User(alice), "join", new NullLogItem());
|
||||
const tiles = mapMessageEntriesToBaseMessageTile(timeline, queue);
|
||||
// 4. subscribe to the queue to observe, and the tiles (so we can safely iterate)
|
||||
const queueObserver = new ListObserver();
|
||||
queue.pendingEvents.subscribe(queueObserver);
|
||||
tiles.subscribe(new ListObserver());
|
||||
const messageTile = findInIterarable(tiles, e => !!e); // the other entries are mapped to null
|
||||
// 5. test toggling
|
||||
assert.equal(messageTile.reactions, null);
|
||||
// 5.1. set reaction, should send a new reaction as there is none yet
|
||||
await messageTile.react("🐶");
|
||||
// now there should be a reactions view model
|
||||
const reactionVM = messageTile.reactions.getReactionViewModelForKey("🐶");
|
||||
let reactionTxnId;
|
||||
{
|
||||
assert.equal(reactionVM.count, 1);
|
||||
const {value: reaction, type} = await queueObserver.next();
|
||||
assert.equal("add", type);
|
||||
assert.equal(reaction.eventType, "m.reaction");
|
||||
assert.equal(reaction.relatedEventId, messageEvent.event_id);
|
||||
// SendQueue puts reaction in sending status, as it is first in the queue
|
||||
assert.equal("update", (await queueObserver.next()).type);
|
||||
reactionTxnId = reaction.txnId;
|
||||
}
|
||||
// 5.2. unset reaction, should redact the previous pending reaction as it has started sending already
|
||||
let redactionIndex;
|
||||
await reactionVM.toggleReaction();
|
||||
{
|
||||
assert.equal(reactionVM.count, 0);
|
||||
const {value: redaction, type, index} = await queueObserver.next();
|
||||
assert.equal("add", type);
|
||||
assert.equal(redaction.eventType, "m.room.redaction");
|
||||
assert.equal(redaction.relatedTxnId, reactionTxnId);
|
||||
redactionIndex = index;
|
||||
}
|
||||
// 5.3. set reaction, should abort the previous pending redaction as it hasn't started sending yet
|
||||
await reactionVM.toggleReaction();
|
||||
{
|
||||
assert.equal(reactionVM.count, 1);
|
||||
const {index, type} = await queueObserver.next();
|
||||
assert.equal("remove", type);
|
||||
assert.equal(redactionIndex, index);
|
||||
redactionIndex = index;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue