2023-08-10 14:59:01 +02:00
/ *
Copyright 2023 The Matrix . org Foundation C . I . C .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2023-09-22 13:57:40 +02:00
/* See readme.md for tips on writing these tests. */
2023-09-18 17:01:05 +02:00
2023-08-10 14:59:01 +02:00
/// <reference types="cypress" />
import { HomeserverInstance } from "../../plugins/utils/homeserver" ;
2023-09-22 13:57:40 +02:00
import {
2023-09-29 10:26:47 +02:00
assertMessageLoaded ,
assertMessageNotLoaded ,
2023-09-22 13:57:40 +02:00
assertRead ,
2023-09-29 10:26:47 +02:00
assertReadThread ,
2023-09-22 12:51:26 +02:00
assertStillRead ,
2023-09-22 13:57:40 +02:00
assertUnread ,
2023-09-29 10:26:47 +02:00
assertUnreadGreaterThan ,
assertUnreadThread ,
closeThreadsPanel ,
2023-09-22 13:57:40 +02:00
customEvent ,
goTo ,
2023-09-29 10:26:47 +02:00
many ,
2023-09-22 13:57:40 +02:00
markAsRead ,
Message ,
2023-09-29 10:26:47 +02:00
MessageContentSpec ,
MessageFinder ,
openThread ,
openThreadList ,
pageUp ,
2023-10-02 17:14:56 +02:00
ReadReceiptSetup ,
2023-09-29 10:26:47 +02:00
saveAndReload ,
2023-09-22 13:57:40 +02:00
sendMessageAsClient ,
} from "./read-receipts-utils" ;
2023-08-10 14:59:01 +02:00
describe ( "Read receipts" , ( ) = > {
const roomAlpha = "Room Alpha" ;
const roomBeta = "Room Beta" ;
let homeserver : HomeserverInstance ;
2023-09-29 10:26:47 +02:00
let messageFinder : MessageFinder ;
2023-10-02 17:14:56 +02:00
let testSetup : ReadReceiptSetup ;
2023-09-29 10:26:47 +02:00
function threadedOff ( rootMessage : string , newMessage : string ) : MessageContentSpec {
return messageFinder . threadedOff ( rootMessage , newMessage ) ;
}
function manyThreadedOff ( rootMessage : string , newMessages : Array < string > ) : Array < MessageContentSpec > {
return messageFinder . manyThreadedOff ( rootMessage , newMessages ) ;
}
function jumpTo ( room : string , message : string , includeThreads = false ) {
return messageFinder . jumpTo ( room , message , includeThreads ) ;
}
2023-09-20 12:57:24 +02:00
2023-08-30 17:15:47 +02:00
before ( ( ) = > {
// Note: unusually for the Cypress tests in this repo, we share a single
// Synapse between all the tests in this file.
//
// Stopping and starting Synapse costs about 0.25 seconds per test, so
// for most suites this is worth the cost for the extra assurance that
// each test is independent.
//
// Because there are so many tests in this file, and because sharing a
// Synapse should have no effect (because we create new rooms and users
// for each test), we share it here, saving ~30 seconds per run at time
// of writing.
2023-08-10 14:59:01 +02:00
cy . startHomeserver ( "default" ) . then ( ( data ) = > {
homeserver = data ;
} ) ;
} ) ;
2023-08-30 17:15:47 +02:00
beforeEach ( ( ) = > {
2023-09-29 10:26:47 +02:00
messageFinder = new MessageFinder ( ) ;
2023-10-02 17:14:56 +02:00
testSetup = new ReadReceiptSetup ( homeserver , "Mae" , "Other User" , roomAlpha , roomBeta ) ;
2023-08-30 17:15:47 +02:00
} ) ;
after ( ( ) = > {
2023-08-10 14:59:01 +02:00
cy . stopHomeserver ( homeserver ) ;
} ) ;
2023-08-15 16:08:53 +02:00
/ * *
* Sends messages into given room as a bot
* @param room - the name of the room to send messages into
* @param messages - the list of messages to send , these can be strings or implementations of MessageSpec like ` editOf `
* /
function receiveMessages ( room : string , messages : Message [ ] ) {
2023-10-02 17:14:56 +02:00
sendMessageAsClient ( testSetup . bot , room , messages ) ;
2023-08-15 16:08:53 +02:00
}
2023-08-10 14:59:01 +02:00
const room1 = roomAlpha ;
const room2 = roomBeta ;
describe ( "Message ordering" , ( ) = > {
describe ( "in the main timeline" , ( ) = > {
it . skip ( "A receipt for the last event in sync order (even with wrong ts) marks a room as read" , ( ) = > { } ) ;
it . skip ( "A receipt for a non-last event in sync order (even when ts makes it last) leaves room unread" , ( ) = > { } ) ;
} ) ;
describe ( "in threads" , ( ) = > {
// These don't pass yet - we need MSC4033 - we don't even know the Sync order yet
it . skip ( "A receipt for the last event in sync order (even with wrong ts) marks a thread as read" , ( ) = > { } ) ;
it . skip ( "A receipt for a non-last event in sync order (even when ts makes it last) leaves thread unread" , ( ) = > { } ) ;
// These pass now and should not later - we should use order from MSC4033 instead of ts
// These are broken out
it . skip ( "A receipt for last threaded event in ts order (even when it was received non-last) marks a thread as read" , ( ) = > { } ) ;
it . skip ( "A receipt for non-last threaded event in ts order (even when it was received last) leaves thread unread" , ( ) = > { } ) ;
it . skip ( "A receipt for last threaded edit in ts order (even when it was received non-last) marks a thread as read" , ( ) = > { } ) ;
it . skip ( "A receipt for non-last threaded edit in ts order (even when it was received last) leaves thread unread" , ( ) = > { } ) ;
it . skip ( "A receipt for last threaded reaction in ts order (even when it was received non-last) marks a thread as read" , ( ) = > { } ) ;
it . skip ( "A receipt for non-last threaded reaction in ts order (even when it was received last) leaves thread unread" , ( ) = > { } ) ;
} ) ;
describe ( "thread roots" , ( ) = > {
it . skip ( "A receipt for last reaction to thread root in sync order (even when ts makes it last) marks room as read" , ( ) = > { } ) ;
it . skip ( "A receipt for non-last reaction to thread root in sync order (even when ts makes it last) leaves room unread" , ( ) = > { } ) ;
it . skip ( "A receipt for last edit to thread root in sync order (even when ts makes it last) marks room as read" , ( ) = > { } ) ;
it . skip ( "A receipt for non-last edit to thread root in sync order (even when ts makes it last) leaves room unread" , ( ) = > { } ) ;
} ) ;
} ) ;
describe ( "Ignored events" , ( ) = > {
2023-08-15 16:08:53 +02:00
it ( "If all events after receipt are unimportant, the room is read" , ( ) = > {
goTo ( room1 ) ;
assertRead ( room2 ) ;
receiveMessages ( room2 , [ "Msg1" , "Msg2" ] ) ;
assertUnread ( room2 , 2 ) ;
markAsRead ( room2 ) ;
2023-09-04 18:08:03 +02:00
assertRead ( room2 ) ;
2023-08-15 16:08:53 +02:00
receiveMessages ( room2 , [ customEvent ( "org.custom.event" , { body : "foobar" } ) ] ) ;
assertRead ( room2 ) ;
} ) ;
it ( "Sending an important event after unimportant ones makes the room unread" , ( ) = > {
2023-09-22 12:51:26 +02:00
// Given We have read the important messages
2023-08-15 16:08:53 +02:00
goTo ( room1 ) ;
assertRead ( room2 ) ;
receiveMessages ( room2 , [ "Msg1" , "Msg2" ] ) ;
assertUnread ( room2 , 2 ) ;
2023-09-22 12:51:26 +02:00
goTo ( room2 ) ;
2023-09-04 18:08:03 +02:00
assertRead ( room2 ) ;
2023-09-22 12:51:26 +02:00
goTo ( room1 ) ;
2023-08-15 16:08:53 +02:00
2023-09-22 12:51:26 +02:00
// When we receive important messages
2023-08-15 16:08:53 +02:00
receiveMessages ( room2 , [ customEvent ( "org.custom.event" , { body : "foobar" } ) ] ) ;
2023-09-22 12:51:26 +02:00
// Then the room is still read
assertStillRead ( room2 ) ;
// And when we receive more important ones
2023-08-15 16:08:53 +02:00
receiveMessages ( room2 , [ "Hello" ] ) ;
2023-09-22 12:51:26 +02:00
// The room is unread again
2023-08-15 16:08:53 +02:00
assertUnread ( room2 , 1 ) ;
} ) ;
2023-08-10 14:59:01 +02:00
it . skip ( "A receipt for the last unimportant event makes the room read, even if all are unimportant" , ( ) = > { } ) ;
} ) ;
describe ( "Paging up" , ( ) = > {
2023-09-29 10:26:47 +02:00
it ( "Paging up through old messages after a room is read leaves the room read" , ( ) = > {
// Given lots of messages are in the room, but we have read them
goTo ( room1 ) ;
receiveMessages ( room2 , many ( "Msg" , 110 ) ) ;
assertUnread ( room2 , 110 ) ;
goTo ( room2 ) ;
assertRead ( room2 ) ;
goTo ( room1 ) ;
// When we restart, so only recent messages are loaded
saveAndReload ( ) ;
goTo ( room2 ) ;
assertMessageNotLoaded ( "Msg0010" ) ;
// And we page up, loading in old messages
pageUp ( ) ;
cy . wait ( 200 ) ;
pageUp ( ) ;
cy . wait ( 200 ) ;
pageUp ( ) ;
assertMessageLoaded ( "Msg0010" ) ;
// Then the room remains read
assertStillRead ( room2 ) ;
} ) ;
it ( "Paging up through old messages of an unread room leaves the room unread" , ( ) = > {
// Given lots of messages are in the room, and they are not read
goTo ( room1 ) ;
receiveMessages ( room2 , many ( "x\ny\nz\nMsg" , 40 ) ) ; // newline to spread out messages
assertUnread ( room2 , 40 ) ;
// When I jump to a message in the middle and page up
jumpTo ( room2 , "x\ny\nz\nMsg0020" ) ;
pageUp ( ) ;
// Then the room is still unread
assertUnreadGreaterThan ( room2 , 1 ) ;
} ) ;
it ( "Paging up to find old threads that were previously read leaves the room read" , ( ) = > {
// Given lots of messages in threads are all read
goTo ( room1 ) ;
receiveMessages ( room2 , [
"Root1" ,
"Root2" ,
"Root3" ,
. . . manyThreadedOff ( "Root1" , many ( "T" , 20 ) ) ,
. . . manyThreadedOff ( "Root2" , many ( "T" , 20 ) ) ,
. . . manyThreadedOff ( "Root3" , many ( "T" , 20 ) ) ,
] ) ;
goTo ( room2 ) ;
assertUnread ( room2 , 60 ) ;
openThread ( "Root1" ) ;
assertUnread ( room2 , 40 ) ;
assertReadThread ( "Root1" ) ;
openThread ( "Root2" ) ;
assertUnread ( room2 , 20 ) ;
assertReadThread ( "Root2" ) ;
openThread ( "Root3" ) ;
assertRead ( room2 ) ;
assertReadThread ( "Root3" ) ;
// When I restart and page up to load old thread roots
goTo ( room1 ) ;
saveAndReload ( ) ;
goTo ( room2 ) ;
pageUp ( ) ;
// Then the room and threads remain read
assertRead ( room2 ) ;
assertReadThread ( "Root1" ) ;
assertReadThread ( "Root2" ) ;
assertReadThread ( "Root3" ) ;
} ) ;
2023-10-05 16:30:49 +02:00
// https://github.com/vector-im/element-web/issues/26294
it . skip ( "Paging up to find old threads that were never read keeps the room unread" , ( ) = > {
2023-09-29 10:26:47 +02:00
// Given lots of messages in threads that are unread
goTo ( room1 ) ;
receiveMessages ( room2 , [
"Root1" ,
"Root2" ,
"Root3" ,
. . . manyThreadedOff ( "Root1" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root2" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root3" , many ( "T" , 2 ) ) ,
. . . many ( "Msg" , 100 ) ,
] ) ;
goTo ( room2 ) ;
assertUnread ( room2 , 6 ) ;
assertUnreadThread ( "Root1" ) ;
assertUnreadThread ( "Root2" ) ;
assertUnreadThread ( "Root3" ) ;
// When I restart
closeThreadsPanel ( ) ;
goTo ( room1 ) ;
saveAndReload ( ) ;
// Then the room remembers it's unread
// TODO: I (andyb) think this will fall in an encrypted room
assertUnread ( room2 , 6 ) ;
// And when I page up to load old thread roots
goTo ( room2 ) ;
pageUp ( ) ;
// Then the room remains unread
assertUnread ( room2 , 6 ) ;
assertUnreadThread ( "Root1" ) ;
assertUnreadThread ( "Root2" ) ;
assertUnreadThread ( "Root3" ) ;
} ) ;
it ( "Looking in thread view to find old threads that were never read makes the room unread" , ( ) = > {
// Given lots of messages in threads that are unread
goTo ( room1 ) ;
receiveMessages ( room2 , [
"Root1" ,
"Root2" ,
"Root3" ,
. . . manyThreadedOff ( "Root1" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root2" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root3" , many ( "T" , 2 ) ) ,
. . . many ( "Msg" , 100 ) ,
] ) ;
goTo ( room2 ) ;
assertUnread ( room2 , 6 ) ;
assertUnreadThread ( "Root1" ) ;
assertUnreadThread ( "Root2" ) ;
assertUnreadThread ( "Root3" ) ;
// When I restart
closeThreadsPanel ( ) ;
goTo ( room1 ) ;
saveAndReload ( ) ;
// Then the room remembers it's unread
// TODO: I (andyb) think this will fall in an encrypted room
assertUnread ( room2 , 6 ) ;
// And when I open the threads view
goTo ( room2 ) ;
openThreadList ( ) ;
// Then the room remains unread
assertUnread ( room2 , 6 ) ;
assertUnreadThread ( "Root1" ) ;
assertUnreadThread ( "Root2" ) ;
assertUnreadThread ( "Root3" ) ;
} ) ;
it ( "After marking room as read, paging up to find old threads that were never read leaves the room read" , ( ) = > {
// Given lots of messages in threads that are unread but I marked as read on a main timeline message
goTo ( room1 ) ;
receiveMessages ( room2 , [
"Root1" ,
"Root2" ,
"Root3" ,
. . . manyThreadedOff ( "Root1" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root2" , many ( "T" , 2 ) ) ,
. . . manyThreadedOff ( "Root3" , many ( "T" , 2 ) ) ,
. . . many ( "Msg" , 100 ) ,
] ) ;
markAsRead ( room2 ) ;
assertRead ( room2 ) ;
// When I restart
saveAndReload ( ) ;
// Then the room remembers it's read
assertRead ( room2 ) ;
// And when I page up to load old thread roots
goTo ( room2 ) ;
pageUp ( ) ;
pageUp ( ) ;
pageUp ( ) ;
// Then the room remains read
assertStillRead ( room2 ) ;
assertReadThread ( "Root1" ) ;
assertReadThread ( "Root2" ) ;
assertReadThread ( "Root3" ) ;
} ) ;
// XXX: fails because we see a dot instead of an unread number - probably the server and client disagree
it . skip ( "After marking room as read based on a thread message, opening threads view to find old threads that were never read leaves the room read" , ( ) = > {
// Given lots of messages in threads that are unread but I marked as read on a thread message
goTo ( room1 ) ;
receiveMessages ( room2 , [
"Root1" ,
"Root2" ,
"Root3" ,
. . . manyThreadedOff ( "Root1" , many ( "T1-" , 2 ) ) ,
. . . manyThreadedOff ( "Root2" , many ( "T2-" , 2 ) ) ,
. . . manyThreadedOff ( "Root3" , many ( "T3-" , 2 ) ) ,
. . . many ( "Msg" , 100 ) ,
threadedOff ( "Msg0099" , "Thread off 99" ) ,
] ) ;
markAsRead ( room2 ) ;
assertRead ( room2 ) ;
// When I restart
saveAndReload ( ) ;
// Then the room remembers it's read
assertRead ( room2 ) ;
// And when I page up to load old thread roots
goTo ( room2 ) ;
openThreadList ( ) ;
// Then the room remains read
assertStillRead ( room2 ) ;
assertReadThread ( "Root1" ) ;
assertReadThread ( "Root2" ) ;
assertReadThread ( "Root3" ) ;
} ) ;
2023-08-10 14:59:01 +02:00
} ) ;
describe ( "Room list order" , ( ) = > {
it . skip ( "Rooms with unread threads appear at the top of room list if 'unread first' is selected" , ( ) = > { } ) ;
} ) ;
describe ( "Notifications" , ( ) = > {
describe ( "in the main timeline" , ( ) = > {
it . skip ( "A new message that mentions me shows a notification" , ( ) = > { } ) ;
it . skip ( "Reading a notifying message reduces the notification count in the room list, space and tab" , ( ) = > { } ) ;
it . skip ( "Reading the last notifying message removes the notification marker from room list, space and tab" , ( ) = > { } ) ;
it . skip ( "Editing a message to mentions me shows a notification" , ( ) = > { } ) ;
it . skip ( "Reading the last notifying edited message removes the notification marker" , ( ) = > { } ) ;
it . skip ( "Redacting a notifying message removes the notification marker" , ( ) = > { } ) ;
} ) ;
describe ( "in threads" , ( ) = > {
it . skip ( "A new threaded message that mentions me shows a notification" , ( ) = > { } ) ;
it . skip ( "Reading a notifying threaded message removes the notification count" , ( ) = > { } ) ;
it . skip ( "Notification count remains steady when reading threads that contain seen notifications" , ( ) = > { } ) ;
it . skip ( "Notification count remains steady when paging up thread view even when threads contain seen notifications" , ( ) = > { } ) ;
it . skip ( "Notification count remains steady when paging up thread view after mark as unread even if older threads contain notifications" , ( ) = > { } ) ;
it . skip ( "Redacting a notifying threaded message removes the notification marker" , ( ) = > { } ) ;
} ) ;
} ) ;
} ) ;