This repository was archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 823
/
Copy pathtimeline.spec.ts
1116 lines (915 loc) · 51 KB
/
timeline.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright 2022 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.
*/
/// <reference types="cypress" />
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
import type { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { Layout } from "../../../src/settings/enums/Layout";
import { MatrixClient } from "../../global";
import Chainable = Cypress.Chainable;
// The avatar size used in the timeline
const AVATAR_SIZE = 30;
// The resize method used in the timeline
const AVATAR_RESIZE_METHOD = "crop";
const ROOM_NAME = "Test room";
const OLD_AVATAR = "avatar_image1";
const NEW_AVATAR = "avatar_image2";
const OLD_NAME = "Alan";
const NEW_NAME = "Alan (away)";
const getEventTilesWithBodies = (): Chainable<JQuery> => {
return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0);
};
const expectDisplayName = (e: JQuery<HTMLElement>, displayName: string): void => {
expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName);
};
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
cy.all([cy.window({ log: false }), cy.getClient()]).then(([win, cli]) => {
const size = AVATAR_SIZE * win.devicePixelRatio;
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
// eslint-disable-next-line no-restricted-properties
cli.mxcUrlToHttp(avatarUrl, size, size, AVATAR_RESIZE_METHOD),
);
});
};
const sendEvent = (roomId: string, html = false): Chainable<ISendEventResponse> => {
const content = {
msgtype: "m.text" as MsgType,
body: "Message",
format: undefined,
formatted_body: undefined,
};
if (html) {
content.format = "org.matrix.custom.html";
content.formatted_body = "<b>Message</b>";
}
return cy.sendEvent(roomId, null, "m.room.message" as EventType, content);
};
describe("Timeline", () => {
let homeserver: HomeserverInstance;
let roomId: string;
let oldAvatarUrl: string;
let newAvatarUrl: string;
beforeEach(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, OLD_NAME).then(() =>
cy.createRoom({ name: ROOM_NAME }).then((_room1Id) => {
roomId = _room1Id;
}),
);
});
});
afterEach(() => {
cy.stopHomeserver(homeserver);
});
describe("useOnlyCurrentProfiles", () => {
beforeEach(() => {
cy.uploadContent(OLD_AVATAR).then(({ content_uri: url }) => {
oldAvatarUrl = url;
cy.setAvatarUrl(url);
});
cy.uploadContent(NEW_AVATAR).then(({ content_uri: url }) => {
newAvatarUrl = url;
});
});
it("should show historical profiles if disabled", () => {
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false);
sendEvent(roomId);
cy.setDisplayName("Alan (away)");
cy.setAvatarUrl(newAvatarUrl);
// XXX: If we send the second event too quickly, there won't be
// enough time for the client to register the profile change
cy.wait(500);
sendEvent(roomId);
cy.viewRoomByName(ROOM_NAME);
const events = getEventTilesWithBodies();
events.should("have.length", 2);
events.each((e, i) => {
if (i === 0) {
expectDisplayName(e, OLD_NAME);
expectAvatar(e, oldAvatarUrl);
} else if (i === 1) {
expectDisplayName(e, NEW_NAME);
expectAvatar(e, newAvatarUrl);
}
});
});
it("should not show historical profiles if enabled", () => {
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true);
sendEvent(roomId);
cy.setDisplayName(NEW_NAME);
cy.setAvatarUrl(newAvatarUrl);
// XXX: If we send the second event too quickly, there won't be
// enough time for the client to register the profile change
cy.wait(500);
sendEvent(roomId);
cy.viewRoomByName(ROOM_NAME);
const events = getEventTilesWithBodies();
events.should("have.length", 2);
events.each((e) => {
expectDisplayName(e, NEW_NAME);
expectAvatar(e, newAvatarUrl);
});
});
});
describe("configure room", () => {
// Exclude timestamp and read marker from snapshots
const percyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
beforeEach(() => {
cy.injectAxe();
});
it("should create and configure a room on IRC layout", () => {
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
cy.get(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='irc']").within(() => {
cy.get(".mx_GenericEventListSummary_summary")
.findByText(OLD_NAME + " created and configured the room.")
.should("exist");
});
cy.get(".mx_IRCLayout").within(() => {
// Check room name line-height is reset
cy.get(".mx_NewRoomIntro h2").should("have.css", "line-height", "normal");
// Check the profile resizer's place
// See: _IRCLayout
// --RoomView_MessageList-padding = 18px (See: _RoomView.pcss)
// --MessageTimestamp-width = 46px (See: _MessageTimestamp.pcss)
// --icon-width = 14px
// --right-padding = 5px
// --name-width = 80px
// --resizer-width = 15px
// --resizer-a11y = 3px
// 18px + 46px + 14px + 5px + 80px + 5px - 15px - 3px
// = 150px
cy.get(".mx_ProfileResizer").should("have.css", "inset-inline-start", "150px");
});
cy.get(".mx_MainSplit").percySnapshotElement("Configured room on IRC layout");
});
it("should have an expanded generic event list summary (GELS) on IRC layout", () => {
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Wait until configuration is finished
cy.get(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='irc']").within(() => {
cy.get(".mx_GenericEventListSummary_summary")
.findByText(OLD_NAME + " created and configured the room.")
.should("exist");
});
cy.get(".mx_GenericEventListSummary").within(() => {
// Click "expand" link button
cy.findByRole("button", { name: "expand" }).click();
// Assert that the "expand" link button worked
cy.findByRole("button", { name: "collapse" }).should("exist");
});
// Check the height of expanded GELS line
cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_GenericEventListSummary_spacer").should(
"have.css",
"line-height",
"18px", // var(--irc-line-height): $font-18px (See: _IRCLayout.pcss)
);
cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on IRC layout", { percyCSS });
});
it("should have an expanded generic event list summary (GELS) on compact modern/group layout", () => {
cy.visit("/#/room/" + roomId);
// Set compact modern layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group).setSettingValue(
"useCompactLayout",
null,
SettingLevel.DEVICE,
true,
);
// Wait until configuration is finished
cy.get(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='group']")
.findByText(OLD_NAME + " created and configured the room.")
.should("exist");
cy.get(".mx_GenericEventListSummary").within(() => {
// Click "expand" link button
cy.findByRole("button", { name: "expand" }).click();
// Assert that the "expand" link button worked
cy.findByRole("button", { name: "collapse" }).should("exist");
});
// Check the height of expanded GELS line
cy.get(".mx_GenericEventListSummary[data-layout=group] .mx_GenericEventListSummary_spacer").should(
"have.css",
"line-height",
"22px", // $font-22px (See: _GenericEventListSummary.pcss)
);
cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on modern layout", { percyCSS });
});
it("should click 'collapse' on the first hovered info event line inside GELS on bubble layout", () => {
// This test checks clickability of the "Collapse" link button, which had been covered with
// MessageActionBar's safe area - https://github.com/vector-im/element-web/issues/22864
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
cy.get(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='bubble']").within(() => {
cy.get(".mx_GenericEventListSummary_summary")
.findByText(OLD_NAME + " created and configured the room.")
.should("exist");
});
cy.get(".mx_GenericEventListSummary").within(() => {
// Click "expand" link button
cy.findByRole("button", { name: "expand" }).click();
// Assert that the "expand" link button worked
cy.findByRole("button", { name: "collapse" }).should("exist");
});
// Make sure spacer is not visible on bubble layout
cy.get(".mx_GenericEventListSummary[data-layout=bubble] .mx_GenericEventListSummary_spacer").should(
"not.be.visible", // See: _GenericEventListSummary.pcss
);
// Exclude timestamp from snapshot
const percyCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
// Save snapshot of expanded generic event list summary on bubble layout
cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on bubble layout", { percyCSS });
cy.get(".mx_GenericEventListSummary").within(() => {
// Click "collapse" link button on the first hovered info event line
cy.get(".mx_GenericEventListSummary_unstyledList .mx_EventTile_info:first-of-type")
.realHover()
.findByRole("toolbar", { name: "Message Actions" })
.should("be.visible");
cy.findByRole("button", { name: "collapse" }).click();
// Assert that "collapse" link button worked
cy.findByRole("button", { name: "expand" }).should("exist");
});
// Save snapshot of collapsed generic event list summary on bubble layout
cy.get(".mx_MainSplit").percySnapshotElement("Collapsed GELS on bubble layout", { percyCSS });
});
it("should add inline start margin to an event line on IRC layout", () => {
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Wait until configuration is finished
cy.get(".mx_RoomView_body .mx_GenericEventListSummary[data-layout='irc']").within(() => {
cy.get(".mx_GenericEventListSummary_summary")
.findByText(OLD_NAME + " created and configured the room.")
.should("exist");
});
// Click "expand" link button
cy.get(".mx_GenericEventListSummary").findByRole("button", { name: "expand" }).click();
// Check the event line has margin instead of inset property
// cf. _EventTile.pcss
// --EventTile_irc_line_info-margin-inline-start
// = calc(var(--name-width) + var(--icon-width) + 1 * var(--right-padding))
// = 80 + 14 + 5 = 99px
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line")
.should("have.css", "margin-inline-start", "99px")
.should("have.css", "inset-inline-start", "0px");
// Exclude timestamp and read marker from snapshot
const percyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
cy.get(".mx_MainSplit").percySnapshotElement("Event line with inline start margin on IRC layout", {
percyCSS,
});
cy.checkA11y();
});
});
describe("message displaying", () => {
beforeEach(() => {
cy.injectAxe();
});
const messageEdit = () => {
cy.contains(".mx_EventTile .mx_EventTile_line", "Message")
.realHover()
.findByRole("toolbar", { name: "Message Actions" })
.findByRole("button", { name: "Edit" })
.click();
cy.findByRole("textbox", { name: "Edit message" }).type("Edit{enter}");
// Assert that the edited message and the link button are found
cy.contains(".mx_EventTile .mx_EventTile_line", "MessageEdit").within(() => {
// Regex patterns due to the edited date
cy.findByRole("button", { name: /Edited at .*? Click to view edits./ });
});
};
it("should align generic event list summary with messages and emote on IRC layout", () => {
// This test aims to check:
// 1. Alignment of collapsed GELS (generic event list summary) and messages
// 2. Alignment of expanded GELS and messages
// 3. Alignment of expanded GELS and placeholder of deleted message
// 4. Alignment of expanded GELS, placeholder of deleted message, and emote
// Exclude timestamp from snapshot of mx_MainSplit
const percyCSS = ".mx_MainSplit .mx_MessageTimestamp { visibility: hidden !important; }";
cy.visit("/#/room/" + roomId);
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Wait until configuration is finished
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
// Send messages
cy.get(".mx_RoomView_body").within(() => {
cy.findByRole("textbox", { name: "Send a message…" }).type("Hello Mr. Bot{enter}");
cy.findByRole("textbox", { name: "Send a message…" }).type("Hello again, Mr. Bot{enter}");
});
// Make sure the second message was sent
cy.get(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent").should("be.visible");
// 1. Alignment of collapsed GELS (generic event list summary) and messages
// Check inline start spacing of collapsed GELS
// See: _EventTile.pcss
// .mx_GenericEventListSummary[data-layout="irc"] > .mx_EventTile_line
// = var(--name-width) + var(--icon-width) + var(--MessageTimestamp-width) + 2 * var(--right-padding)
// = 80 + 14 + 46 + 2 * 5
// = 150px
cy.get(".mx_GenericEventListSummary[data-layout=irc] > .mx_EventTile_line").should(
"have.css",
"padding-inline-start",
"150px",
);
// Check width and spacing values of elements in .mx_EventTile, which should be equal to 150px
// --right-padding should be applied
cy.get(".mx_EventTile > *").should("have.css", "margin-right", "5px");
// --name-width width zero inline end margin should be applied
cy.get(".mx_EventTile .mx_DisambiguatedProfile")
.should("have.css", "width", "80px")
.should("have.css", "margin-inline-end", "0px");
// --icon-width should be applied
cy.get(".mx_EventTile .mx_EventTile_avatar > .mx_BaseAvatar").should("have.css", "width", "14px");
// var(--MessageTimestamp-width) should be applied
cy.get(".mx_EventTile > a").should("have.css", "min-width", "46px");
// Record alignment of collapsed GELS and messages on messagePanel
cy.get(".mx_MainSplit").percySnapshotElement("Collapsed GELS and messages on IRC layout", { percyCSS });
// 2. Alignment of expanded GELS and messages
// Click "expand" link button
cy.get(".mx_GenericEventListSummary").findByRole("button", { name: "expand" }).click();
// Check inline start spacing of info line on expanded GELS
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info:first-of-type .mx_EventTile_line")
// See: _EventTile.pcss
// --EventTile_irc_line_info-margin-inline-start
// = 80 + 14 + 1 * 5
.should("have.css", "margin-inline-start", "99px");
// Record alignment of expanded GELS and messages on messagePanel
cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS and messages on IRC layout", { percyCSS });
// 3. Alignment of expanded GELS and placeholder of deleted message
// Delete the second (last) message
cy.get(".mx_RoomView_MessageList > .mx_EventTile_last")
.realHover()
.findByRole("button", { name: "Options" })
.should("be.visible")
.click();
cy.findByRole("menuitem", { name: "Remove" }).should("be.visible").click();
// Confirm deletion
cy.get(".mx_Dialog_buttons").within(() => {
cy.findByRole("button", { name: "Remove" }).click();
});
// Make sure the dialog was closed and the second (last) message was redacted
cy.get(".mx_Dialog").should("not.exist");
cy.get(".mx_GenericEventListSummary .mx_EventTile_last .mx_RedactedBody").should("be.visible");
cy.get(".mx_GenericEventListSummary .mx_EventTile_last .mx_EventTile_receiptSent").should("be.visible");
// Record alignment of expanded GELS and placeholder of deleted message on messagePanel
cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS and with placeholder of deleted message", {
percyCSS,
});
// 4. Alignment of expanded GELS, placeholder of deleted message, and emote
// Send a emote
cy.get(".mx_RoomView_body").within(() => {
cy.findByRole("textbox", { name: "Send a message…" }).type("/me says hello to Mr. Bot{enter}");
});
// Check inline start margin of its avatar
// Here --right-padding is for the avatar on the message line
// See: _IRCLayout.pcss
// .mx_IRCLayout .mx_EventTile_emote .mx_EventTile_avatar
// = calc(var(--name-width) + var(--icon-width) + 1 * var(--right-padding))
// = 80 + 14 + 1 * 5
cy.get(".mx_EventTile_emote .mx_EventTile_avatar").should("have.css", "margin-left", "99px");
// Make sure emote was sent
cy.get(".mx_EventTile_last.mx_EventTile_emote .mx_EventTile_receiptSent").should("be.visible");
// Record alignment of expanded GELS, placeholder of deleted message, and emote
cy.get(".mx_MainSplit").percySnapshotElement(
"Expanded GELS and with emote and placeholder of deleted message",
{
percyCSS,
},
);
});
it("should render EventTiles on IRC, modern (group), and bubble layout", () => {
const percyCSS =
// Hide because flaky - See https://github.com/vector-im/element-web/issues/24957
".mx_TopUnreadMessagesBar, " +
// Exclude timestamp and read marker from snapshots
".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
sendEvent(roomId);
sendEvent(roomId); // check continuation
sendEvent(roomId); // check the last EventTile
cy.visit("/#/room/" + roomId);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IRC layout
////////////////////////////////////////////////////////////////////////////////////////////////////////////
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Wait until configuration is finished
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
cy.get(".mx_RoomView_body[data-layout=irc]").within(() => {
// Ensure CSS declarations which cannot be detected with a screenshot test are applied as expected
cy.get(".mx_EventTile")
.should("have.css", "max-width", "100%")
.should("have.css", "clear", "both")
.should("have.css", "position", "relative");
// Check mx_EventTile_continuation
// Block start padding of the second message should not be overridden
cy.get(".mx_EventTile_continuation").should("have.css", "padding-block-start", "0px");
cy.get(".mx_EventTile_continuation .mx_EventTile_line").should("have.css", "clear", "both");
// Select the last event tile
cy.get(".mx_EventTile_last")
.within(() => {
// The last tile is also a continued one
cy.get(".mx_EventTile_line").should("have.css", "clear", "both");
})
// Check that zero block padding is set
.should("have.css", "padding-block-start", "0px");
});
cy.get(".mx_MainSplit").percySnapshotElement("EventTiles on IRC layout", { percyCSS });
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Group/modern layout
////////////////////////////////////////////////////////////////////////////////////////////////////////////
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group);
cy.get(".mx_RoomView_body[data-layout=group]").within(() => {
// Ensure CSS declarations which cannot be detected with a screenshot test are applied as expected
cy.get(".mx_EventTile")
.should("have.css", "max-width", "100%")
.should("have.css", "clear", "both")
.should("have.css", "position", "relative");
// Check mx_EventTile_continuation
// Block start padding of the second message should not be overridden
cy.get(".mx_EventTile_continuation").should("have.css", "padding-block-start", "0px");
cy.get(".mx_EventTile_continuation .mx_EventTile_line").should("have.css", "clear", "both");
// Check that the last EventTile is rendered
cy.get(".mx_EventTile.mx_EventTile_last").should("exist");
});
cy.get(".mx_MainSplit").percySnapshotElement("EventTiles on modern layout", { percyCSS });
// Check the same thing for compact layout
cy.setSettingValue("useCompactLayout", null, SettingLevel.DEVICE, true);
cy.get(".mx_MatrixChat_useCompactLayout").within(() => {
// Ensure CSS declarations which cannot be detected with a screenshot test are applied as expected
cy.get(".mx_EventTile")
.should("have.css", "max-width", "100%")
.should("have.css", "clear", "both")
.should("have.css", "position", "relative");
// Check cascading works
cy.get(".mx_EventTile_continuation").should("have.css", "padding-block-start", "0px");
// Check that the last EventTile is rendered
cy.get(".mx_EventTile.mx_EventTile_last").should("exist");
});
cy.get(".mx_MainSplit").percySnapshotElement("EventTiles on compact modern layout", { percyCSS });
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Message bubble layout
////////////////////////////////////////////////////////////////////////////////////////////////////////////
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
cy.get(".mx_RoomView_body[data-layout=bubble]").within(() => {
// Ensure CSS declarations which cannot be detected with a screenshot test are applied as expected
cy.get(".mx_EventTile")
.should("have.css", "max-width", "none")
.should("have.css", "clear", "both")
.should("have.css", "position", "relative");
// Check that block start padding of the second message is not overridden
cy.get(".mx_EventTile.mx_EventTile_continuation").should("have.css", "margin-block-start", "2px");
// Select the last bubble
cy.get(".mx_EventTile_last")
.within(() => {
// calc(var(--gutterSize) - 1px)
cy.get(".mx_EventTile_line").should("have.css", "padding-block-start", "10px");
})
.should("have.css", "margin-block-start", "2px"); // The last bubble is also a continued one
});
cy.get(".mx_MainSplit").percySnapshotElement("EventTiles on bubble layout", { percyCSS });
});
it("should set inline start padding to a hidden event line", () => {
sendEvent(roomId);
cy.visit("/#/room/" + roomId);
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
// Edit message
messageEdit();
// Click timestamp to highlight hidden event line
cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
// Exclude timestamp and read marker from snapshot
//const percyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
// should not add inline start padding to a hidden event line on IRC layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
cy.get(".mx_EventTile[data-layout=irc].mx_EventTile_info .mx_EventTile_line").should(
"have.css",
"padding-inline-start",
"0px",
);
// Disabled because flaky - see https://github.com/vector-im/element-web/issues/24881
/*cy.get(".mx_MainSplit").percySnapshotElement("Hidden event line with zero padding on IRC layout", {
percyCSS,
});*/
// should add inline start padding to a hidden event line on modern layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group);
cy.get(".mx_EventTile[data-layout=group].mx_EventTile_info .mx_EventTile_line")
// calc(var(--EventTile_group_line-spacing-inline-start) + 20px) = 64 + 20 = 84px
.should("have.css", "padding-inline-start", "84px");
// Disabled because flaky - see https://github.com/vector-im/element-web/issues/24881
//cy.get(".mx_MainSplit").percySnapshotElement("Hidden event line with padding on modern layout", {
// percyCSS,
//});
});
it("should click view source event toggle", () => {
// This test checks:
// 1. clickability of top left of view source event toggle
// 2. clickability of view source toggle on IRC layout
// Exclude timestamp from snapshot
const percyCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
sendEvent(roomId);
cy.visit("/#/room/" + roomId);
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
// Edit message
messageEdit();
// 1. clickability of top left of view source event toggle
// Click top left of the event toggle, which should not be covered by MessageActionBar's safe area
cy.get(".mx_EventTile_last[data-layout=group] .mx_ViewSourceEvent")
.should("exist")
.realHover()
.within(() => {
cy.findByRole("button", { name: "toggle event" }).click("topLeft");
});
// Make sure the expand toggle works
cy.get(".mx_EventTile_last[data-layout=group] .mx_ViewSourceEvent_expanded")
.should("be.visible")
.realHover()
.within(() => {
cy.findByRole("button", { name: "toggle event" })
// Check size and position of toggle on expanded view source event
// See: _ViewSourceEvent.pcss
.should("have.css", "height", "12px") // --ViewSourceEvent_toggle-size
.should("have.css", "align-self", "flex-end")
// Click again to collapse the source
.click("topLeft");
});
// Make sure the collapse toggle works
cy.get(".mx_EventTile_last[data-layout=group] .mx_ViewSourceEvent_expanded").should("not.exist");
// 2. clickability of view source toggle on IRC layout
// Enable IRC layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Hover the view source toggle on IRC layout
cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_EventTile .mx_ViewSourceEvent")
.should("exist")
.realHover()
.percySnapshotElement("Hovered hidden event line on IRC layout", { percyCSS });
// Click view source event toggle
cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_EventTile .mx_ViewSourceEvent")
.should("exist")
.realHover()
.within(() => {
cy.findByRole("button", { name: "toggle event" }).click("topLeft");
});
// Make sure the expand toggle worked
cy.get(".mx_EventTile[data-layout=irc] .mx_ViewSourceEvent_expanded").should("be.visible");
});
it("should render file size in kibibytes on a file tile", () => {
cy.visit("/#/room/" + roomId);
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
// Upload a file from the message composer
cy.get(".mx_MessageComposer_actions input[type='file']").selectFile(
"cypress/fixtures/matrix-org-client-versions.json",
{ force: true },
);
cy.get(".mx_Dialog").within(() => {
// Click "Upload" button
cy.findByRole("button", { name: "Upload" }).click();
});
// Wait until the file is sent
cy.get(".mx_RoomView_statusArea_expanded").should("not.exist");
cy.get(".mx_EventTile.mx_EventTile_last .mx_EventTile_receiptSent").should("exist");
// Assert that the file size is displayed in kibibytes (1024 bytes), not kilobytes (1000 bytes)
// See: https://github.com/vector-im/element-web/issues/24866
cy.get(".mx_EventTile_last").within(() => {
// actual file size in kibibytes
cy.get(".mx_MFileBody_info_filename")
.findByText(/1.12 KB/)
.should("exist");
});
});
it("should render url previews", () => {
cy.intercept("**/_matrix/media/r0/thumbnail/matrix.org/2022-08-16_yaiSVSRIsNFfxDnV?*", {
statusCode: 200,
fixture: "riot.png",
headers: {
"Content-Type": "image/png",
},
}).as("mxc");
cy.intercept("**/_matrix/media/r0/preview_url?url=https%3A%2F%2Fcall.element.io%2F&ts=*", {
statusCode: 200,
body: {
"og:title": "Element Call",
"og:description": null,
"og:image:width": 48,
"og:image:height": 48,
"og:image": "mxc://matrix.org/2022-08-16_yaiSVSRIsNFfxDnV",
"og:image:type": "image/png",
"matrix:image:size": 2121,
},
headers: {
"Content-Type": "application/json",
},
}).as("preview_url");
cy.sendEvent(roomId, null, "m.room.message" as EventType, {
msgtype: "m.text" as MsgType,
body: "https://call.element.io/",
});
cy.visit("/#/room/" + roomId);
cy.get(".mx_LinkPreviewWidget").should("exist").findByText("Element Call");
cy.wait("@preview_url");
cy.wait("@mxc");
cy.checkA11y();
// Exclude timestamp and read marker from snapshot
const percyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
cy.get(".mx_EventTile_last").percySnapshotElement("URL Preview", {
percyCSS,
widths: [800, 400],
});
});
describe("on search results panel", () => {
it("should highlight search result words regardless of formatting", () => {
sendEvent(roomId);
sendEvent(roomId, true);
cy.visit("/#/room/" + roomId);
cy.get(".mx_RoomHeader").findByRole("button", { name: "Search" }).click();
cy.get(".mx_SearchBar").percySnapshotElement("Search bar on the timeline", {
// Emulate narrow timeline
widths: [320, 640],
});
cy.get(".mx_SearchBar_input").findByRole("textbox").type("Message{enter}");
cy.get(".mx_EventTile:not(.mx_EventTile_contextual) .mx_EventTile_searchHighlight").should("exist");
cy.get(".mx_RoomView_searchResultsPanel").percySnapshotElement("Highlighted search results");
});
it("should render a fully opaque textual event", () => {
const stringToSearch = "Message"; // Same with string sent with sendEvent()
sendEvent(roomId);
cy.visit("/#/room/" + roomId);
// Open a room setting dialog
cy.findByRole("button", { name: "Room options" }).click();
cy.findByRole("menuitem", { name: "Settings" }).click();
// Set a room topic to render a TextualEvent
cy.findByRole("textbox", { name: "Room Topic" }).type(`This is a room for ${stringToSearch}.`);
cy.findByRole("button", { name: "Save" }).click();
cy.closeDialog();
// Assert that the TextualEvent is rendered
cy.findByText(`${OLD_NAME} changed the topic to "This is a room for ${stringToSearch}.".`)
.should("exist")
.should("have.class", "mx_TextualEvent");
// Display the room search bar
cy.get(".mx_RoomHeader").findByRole("button", { name: "Search" }).click();
// Search the string to display both the message and TextualEvent on search results panel
cy.get(".mx_SearchBar").within(() => {
cy.findByRole("textbox").type(`${stringToSearch}{enter}`);
});
// On search results panel
cy.get(".mx_RoomView_searchResultsPanel").within(() => {
// Assert that contextual event tiles are translucent
cy.get(".mx_EventTile.mx_EventTile_contextual").should("have.css", "opacity", "0.4");
// Assert that the TextualEvent is fully opaque (visually solid).
cy.get(".mx_EventTile .mx_TextualEvent").should("have.css", "opacity", "1");
});
cy.get(".mx_RoomView_searchResultsPanel").percySnapshotElement("Search results - with TextualEvent");
});
});
});
describe("message sending", () => {
const MESSAGE = "Hello world";
const reply = "Reply";
const viewRoomSendMessageAndSetupReply = () => {
// View room
cy.visit("/#/room/" + roomId);
// Send a message
cy.getComposer().type(`${MESSAGE}{enter}`);
// Reply to the message
cy.get(".mx_EventTile_last")
.within(() => {
cy.findByText(MESSAGE);
})
.realHover()
.findByRole("button", { name: "Reply" })
.click();
};
// For clicking the reply button on the last line
const clickButtonReply = () => {
cy.get(".mx_RoomView_MessageList").within(() => {
cy.get(".mx_EventTile_last").realHover().findByRole("button", { name: "Reply" }).click();
});
};
it("can reply with a text message", () => {
viewRoomSendMessageAndSetupReply();
cy.getComposer().type(`${reply}{enter}`);
cy.get(".mx_RoomView_body").within(() => {
cy.get(".mx_EventTile_last .mx_EventTile_line").within(() => {
cy.get(".mx_ReplyTile .mx_MTextBody").within(() => {
cy.findByText(MESSAGE).should("exist");
});
cy.findByText(reply).should("have.length", 1);
});
});
});
it("can reply with a voice message", () => {
viewRoomSendMessageAndSetupReply();
cy.openMessageComposerOptions().within(() => {
cy.findByRole("menuitem", { name: "Voice Message" }).click();
});
// Record an empty message
cy.wait(3000);
cy.get(".mx_RoomView_body").within(() => {
cy.get(".mx_MessageComposer").findByRole("button", { name: "Send voice message" }).click();
cy.get(".mx_EventTile_last .mx_EventTile_line").within(() => {
cy.get(".mx_ReplyTile .mx_MTextBody").within(() => {
cy.findByText(MESSAGE).should("exist");
});
cy.get(".mx_MVoiceMessageBody").should("have.length", 1);
});
});
});
it("should not be possible to send flag with regional emojis", () => {
cy.visit("/#/room/" + roomId);
// Send a message
cy.getComposer().type(":regional_indicator_a");
cy.contains(".mx_Autocomplete_Completion_title", ":regional_indicator_a:").click();
cy.getComposer().type(":regional_indicator_r");
cy.contains(".mx_Autocomplete_Completion_title", ":regional_indicator_r:").click();
cy.getComposer().type(" :regional_indicator_z");
cy.contains(".mx_Autocomplete_Completion_title", ":regional_indicator_z:").click();
cy.getComposer().type(":regional_indicator_a");
cy.contains(".mx_Autocomplete_Completion_title", ":regional_indicator_a:").click();
cy.getComposer().type("{enter}");
cy.get(".mx_RoomView_body .mx_EventTile .mx_EventTile_line .mx_MTextBody .mx_EventTile_bigEmoji")
.children()
.should("have.length", 4);
});
it("should display a reply chain", () => {
let bot: MatrixClient;
const reply2 = "Reply again";
cy.visit("/#/room/" + roomId);
// Wait until configuration is finished
cy.get(".mx_GenericEventListSummary_summary").within(() => {
cy.findByText(OLD_NAME + " created and configured the room.").should("exist");
});
// Create a bot "BotBob" and invite it
cy.getBot(homeserver, {
displayName: "BotBob",
autoAcceptInvites: false,
}).then((_bot) => {
bot = _bot;
cy.inviteUser(roomId, bot.getUserId());
bot.joinRoom(roomId);
// Make sure the bot joined the room
cy.get(".mx_GenericEventListSummary .mx_EventTile_info.mx_EventTile_last").within(() => {
cy.findByText("BotBob joined the room").should("exist");
});
// Have bot send MESSAGE to roomId
cy.botSendMessage(bot, roomId, MESSAGE);
});
// Assert that MESSAGE is found
cy.findByText(MESSAGE);
// Reply to the message
clickButtonReply();
cy.getComposer().type(`${reply}{enter}`);
// Make sure 'reply' was sent
cy.get(".mx_RoomView_body .mx_EventTile_last").within(() => {
cy.findByText(reply).should("exist");
});
// Reply again to create a replyChain
clickButtonReply();
cy.getComposer().type(`${reply2}{enter}`);
// Assert that 'reply2' was sent
cy.get(".mx_RoomView_body .mx_EventTile_last").within(() => {
cy.findByText(reply2).should("exist");
});
cy.get(".mx_EventTile_last .mx_EventTile_receiptSent").should("be.visible");
// Exclude timestamp and read marker from snapshot
const percyCSS = ".mx_MessageTimestamp, .mx_MessagePanel_myReadMarker { visibility: hidden !important; }";
// Check the margin value of ReplyChains of EventTile at the bottom on IRC layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
cy.get(".mx_EventTile_last[data-layout='irc'] .mx_ReplyChain").should("have.css", "margin", "0px");
// Take a snapshot on IRC layout
// Note that because zero margin is applied to mx_ReplyChain, the left borders of two mx_ReplyChain
// components may seem to be connected to one.
cy.get(".mx_EventTile_last").percySnapshotElement("EventTile with reply chains on IRC layout", {
percyCSS,
});
// Check the margin value of ReplyChains of EventTile at the bottom on group/modern layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Group);
cy.get(".mx_EventTile_last[data-layout='group'] .mx_ReplyChain").should("have.css", "margin-bottom", "8px");
// Take a snapshot on modern layout
cy.get(".mx_EventTile_last").percySnapshotElement("EventTile with reply chains on modern layout", {
percyCSS,
});
// Check the margin value of ReplyChains of EventTile at the bottom on group/modern compact layout
cy.setSettingValue("useCompactLayout", null, SettingLevel.DEVICE, true);
cy.get(".mx_EventTile_last[data-layout='group'] .mx_ReplyChain").should("have.css", "margin-bottom", "4px");
// Take a snapshot on compact modern layout
cy.get(".mx_EventTile_last").percySnapshotElement("EventTile with reply chains on compact modern layout", {
percyCSS,
});
// Check the margin value of ReplyChains of EventTile at the bottom on bubble layout
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
cy.get(".mx_EventTile_last[data-layout='bubble'] .mx_ReplyChain").should(