Merge remote-tracking branch 'origin/develop' into feat/matrix-wysisyg-integration

This commit is contained in:
Florian Duros 2022-10-10 17:04:27 +02:00
commit 5bdac78fc7
No known key found for this signature in database
GPG key ID: 9700AA5870258A0B
150 changed files with 3632 additions and 980 deletions

View file

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
exports[`RoomView for a local room in state ERROR should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_RoomStatusBar mx_RoomStatusBar_unsentMessages\\"><div role=\\"alert\\"><div class=\\"mx_RoomStatusBar_unsentBadge\\"><div class=\\"mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char\\"><span class=\\"mx_NotificationBadge_count\\">!</span></div></div><div><div class=\\"mx_RoomStatusBar_unsentTitle\\">Some of your messages have not been sent</div></div><div class=\\"mx_RoomStatusBar_unsentButtonBar\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_RoomStatusBar_unsentRetry\\">Retry</div></div></div></div></main></div>"`;
exports[`RoomView for a local room in state ERROR should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_RoomStatusBar mx_RoomStatusBar_unsentMessages\\"><div role=\\"alert\\"><div class=\\"mx_RoomStatusBar_unsentBadge\\"><div class=\\"mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char\\"><span class=\\"mx_NotificationBadge_count\\">!</span></div></div><div><div class=\\"mx_RoomStatusBar_unsentTitle\\">Some of your messages have not been sent</div></div><div class=\\"mx_RoomStatusBar_unsentButtonBar\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_RoomStatusBar_unsentRetry\\">Retry</div></div></div></div></main></div>"`;
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_RoomHeader_e2eIcon\\"><div class=\\"mx_E2EIcon mx_E2EIcon_normal\\"></div></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;

View file

@ -27,7 +27,7 @@ import {
EventType,
} from 'matrix-js-sdk/src/matrix';
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
import { Thread } from "matrix-js-sdk/src/models/thread";
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
import { mocked } from "jest-mock";
import { act } from '@testing-library/react';
@ -469,7 +469,7 @@ describe('MessageContextMenu', () => {
const eventContent = MessageEvent.from("hello");
const mxEvent = new MatrixEvent(eventContent.serialize());
Thread.hasServerSideSupport = true;
Thread.hasServerSideSupport = FeatureSupport.Stable;
const context = {
canSendMessages: true,
};

View file

@ -4,7 +4,7 @@ exports[`<TooltipTarget /> displays Bottom aligned tooltip on mouseover 1`] = `
<div
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
role="tooltip"
style="display: block; top: 6px; left: 0px; transform: translate(-50%);"
style="display: block; top: 6px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))));"
>
<div
class="mx_Tooltip_chevron"
@ -17,7 +17,7 @@ exports[`<TooltipTarget /> displays InnerBottom aligned tooltip on mouseover 1`]
<div
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
role="tooltip"
style="display: block; top: -50px; left: 0px; transform: translate(-50%);"
style="display: block; top: -50px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))));"
>
<div
class="mx_Tooltip_chevron"
@ -69,7 +69,7 @@ exports[`<TooltipTarget /> displays Top aligned tooltip on mouseover 1`] = `
<div
class="mx_Tooltip test tooltipClassName mx_Tooltip_visible"
role="tooltip"
style="display: block; top: -6px; left: 0px; transform: translate(-50%, -100%);"
style="display: block; top: -6px; transform: translate(max(10px, min(calc(0px - 50%), calc(100vw - 100% - 10px))), -100%);"
>
<div
class="mx_Tooltip_chevron"

View file

@ -42,36 +42,53 @@ exports[`<LocationShareMenu /> with live location disabled goes to labs flag scr
Enable live location sharing
</span>
<_default
aria-label="Enable live location sharing"
checked={false}
onChange={[Function]}
title="Enable live location sharing"
>
<AccessibleButton
<AccessibleTooltipButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable live location sharing"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onClick={[Function]}
role="switch"
tabIndex={0}
title="Enable live location sharing"
>
<div
<AccessibleButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable live location sharing"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
aria-checked={false}
aria-disabled={false}
aria-label="Enable live location sharing"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
</AccessibleTooltipButton>
</_default>
</div>
</LabelledToggleSwitch>

View file

@ -82,7 +82,7 @@ describe("CallEvent", () => {
));
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.get(room.roomId);
const maybeCall = CallStore.instance.getCall(room.roomId);
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
call = maybeCall;
@ -113,7 +113,7 @@ describe("CallEvent", () => {
});
it("shows placeholder info if the call isn't loaded yet", () => {
jest.spyOn(CallStore.instance, "get").mockReturnValue(null);
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(null);
jest.advanceTimersByTime(90000);
renderEvent();

View file

@ -25,7 +25,7 @@ import {
MsgType,
Room,
} from 'matrix-js-sdk/src/matrix';
import { Thread } from 'matrix-js-sdk/src/models/thread';
import { FeatureSupport, Thread } from 'matrix-js-sdk/src/models/thread';
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
import {
@ -388,13 +388,13 @@ describe('<MessageActionBar />', () => {
describe('thread button', () => {
beforeEach(() => {
Thread.setServerSideSupport(true, false);
Thread.setServerSideSupport(FeatureSupport.Stable);
});
describe('when threads feature is not enabled', () => {
it('does not render thread button when threads does not have server support', () => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
Thread.setServerSideSupport(false, false);
Thread.setServerSideSupport(FeatureSupport.None);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Reply in thread')).toBeFalsy();
});

View file

@ -268,6 +268,7 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
liveTimeline: undefined,
resizing: false,
narrow,
activeCall: null,
};
}

View file

@ -25,6 +25,8 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import EventEmitter from "events";
import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -53,6 +55,10 @@ import LegacyCallHandler from "../../../../src/LegacyCallHandler";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
import WidgetStore from "../../../../src/stores/WidgetStore";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import WidgetUtils from "../../../../src/utils/WidgetUtils";
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
describe('RoomHeader (Enzyme)', () => {
it('shows the room avatar in a room with only ourselves', () => {
@ -173,13 +179,13 @@ describe('RoomHeader (Enzyme)', () => {
it("should render buttons if not passing showButtons (default true)", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room);
expect(wrapper.find(".mx_RoomHeader_buttons")).toHaveLength(1);
expect(wrapper.find(".mx_RoomHeader_button")).not.toHaveLength(0);
});
it("should not render buttons if passing showButtons = false", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, { showButtons: false });
expect(wrapper.find(".mx_RoomHeader_buttons")).toHaveLength(0);
expect(wrapper.find(".mx_RoomHeader_button")).toHaveLength(0);
});
it("should render the room options context menu if not passing enableRoomOptionsMenu (default true)", () => {
@ -252,6 +258,8 @@ function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoom
searchScope: SearchScope.Room,
searchCount: 0,
},
viewingCall: false,
activeCall: null,
...propsOverride,
};
@ -381,6 +389,12 @@ describe("RoomHeader (React Testing Library)", () => {
await Promise.all([CallStore.instance, WidgetStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
));
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [],
[MediaDeviceKindEnum.VideoInput]: [],
[MediaDeviceKindEnum.AudioOutput]: [],
});
});
afterEach(async () => {
@ -419,6 +433,32 @@ describe("RoomHeader (React Testing Library)", () => {
const mockLegacyCall = () => {
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue({} as unknown as MatrixCall);
};
const withCall = async (fn: (call: ElementCall) => (void | Promise<void>)): Promise<void> => {
await ElementCall.create(room);
const call = CallStore.instance.getCall(room.roomId);
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
const widget = new Widget(call.widget);
const eventEmitter = new EventEmitter();
const messaging = {
on: eventEmitter.on.bind(eventEmitter),
off: eventEmitter.off.bind(eventEmitter),
once: eventEmitter.once.bind(eventEmitter),
emit: eventEmitter.emit.bind(eventEmitter),
stop: jest.fn(),
transport: {
send: jest.fn(),
reply: jest.fn(),
},
} as unknown as Mocked<ClientWidgetApi>;
WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
await fn(call);
call.destroy();
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
};
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => {
render(
@ -437,6 +477,8 @@ describe("RoomHeader (React Testing Library)", () => {
searchScope: SearchScope.Room,
searchCount: 0,
}}
viewingCall={false}
activeCall={null}
{...props}
/>
</RoomContext.Provider>,
@ -463,7 +505,9 @@ describe("RoomHeader (React Testing Library)", () => {
+ "and there's an ongoing call",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
await ElementCall.create(room);
renderHeader();
@ -477,7 +521,9 @@ describe("RoomHeader (React Testing Library)", () => {
+ "use Element Call exclusively",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
renderHeader();
expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
@ -499,7 +545,9 @@ describe("RoomHeader (React Testing Library)", () => {
+ "and the user lacks permission",
() => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put({ element_call: { url: "https://call.element.io", use_exclusively: true } });
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100 });
renderHeader();
@ -724,4 +772,99 @@ describe("RoomHeader (React Testing Library)", () => {
expect(screen.getByRole("button", { name: "Voice call" })).toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("button", { name: "Video call" })).toHaveAttribute("aria-disabled", "true");
});
it("shows a close button when viewing a call lobby that returns to the timeline when pressed", async () => {
mockEnabledSettings(["feature_group_calls"]);
renderHeader({ viewingCall: true });
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: /close/i }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}));
defaultDispatcher.unregister(dispatcherRef);
});
it("shows a reduce button when viewing a call that returns to the timeline when pressed", async () => {
mockEnabledSettings(["feature_group_calls"]);
await withCall(async call => {
renderHeader({ viewingCall: true, activeCall: call });
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: /timeline/i }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}));
defaultDispatcher.unregister(dispatcherRef);
});
});
it("shows a layout button when viewing a call that shows a menu when pressed", async () => {
mockEnabledSettings(["feature_group_calls"]);
await withCall(async call => {
await call.connect();
const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(call.widget));
renderHeader({ viewingCall: true, activeCall: call });
// Should start with Freedom selected
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
// Clicking Spotlight should tell the widget to switch and close the menu
fireEvent.click(screen.getByRole("menuitemradio", { name: "Spotlight" }));
expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
expect(screen.queryByRole("menu")).toBeNull();
// When the widget responds and the user reopens the menu, they should see Spotlight selected
act(() => {
messaging.emit(
`action:${ElementWidgetActions.SpotlightLayout}`,
new CustomEvent("widgetapirequest", { detail: { data: {} } }),
);
});
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
screen.getByRole("menuitemradio", { name: "Spotlight", checked: true });
// Now try switching back to Freedom
fireEvent.click(screen.getByRole("menuitemradio", { name: "Freedom" }));
expect(mocked(messaging.transport).send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {});
expect(screen.queryByRole("menu")).toBeNull();
// When the widget responds and the user reopens the menu, they should see Freedom selected
act(() => {
messaging.emit(
`action:${ElementWidgetActions.TileLayout}`,
new CustomEvent("widgetapirequest", { detail: { data: {} } }),
);
});
fireEvent.click(screen.getByRole("button", { name: /layout/i }));
screen.getByRole("menuitemradio", { name: "Freedom", checked: true });
});
});
it("shows an invite button in video rooms", () => {
mockEnabledSettings(["feature_video_rooms", "feature_element_call_video_rooms"]);
mockRoomType(RoomType.UnstableCall);
const onInviteClick = jest.fn();
renderHeader({ onInviteClick, viewingCall: true });
fireEvent.click(screen.getByRole("button", { name: /invite/i }));
expect(onInviteClick).toHaveBeenCalled();
});
it("hides the invite button in non-video rooms when viewing a call", () => {
renderHeader({ onInviteClick: () => {}, viewingCall: true });
expect(screen.queryByRole("button", { name: /invite/i })).toBeNull();
});
});

View file

@ -77,7 +77,7 @@ describe("RoomTile", () => {
setupAsyncStoreWithClient(WidgetMessagingStore.instance, client);
MockedCall.create(room, "1");
call = CallStore.instance.get(room.roomId) as MockedCall;
call = CallStore.instance.getCall(room.roomId) as MockedCall;
widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {

View file

@ -91,6 +91,7 @@ describe('<SendMessageComposer/>', () => {
canSelfRedact: false,
resizing: false,
narrow: false,
activeCall: null,
};
describe("createMessageContent", () => {
const permalinkCreator = jest.fn() as any;

View file

@ -112,16 +112,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
data-testid="device-tile-device_1"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -235,16 +239,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
data-testid="device-tile-device_2"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -317,16 +325,20 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
data-testid="device-tile-device_3"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>

View file

@ -18,37 +18,54 @@ exports[`<Notifications /> main notification switches email switches renders ema
Enable email notifications for tester@test.com
</span>
<_default
aria-label="Enable email notifications for tester@test.com"
checked={false}
disabled={false}
onChange={[Function]}
title="Enable email notifications for tester@test.com"
>
<AccessibleButton
<AccessibleTooltipButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable email notifications for tester@test.com"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onClick={[Function]}
role="switch"
tabIndex={0}
title="Enable email notifications for tester@test.com"
>
<div
<AccessibleButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable email notifications for tester@test.com"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
aria-checked={false}
aria-disabled={false}
aria-label="Enable email notifications for tester@test.com"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
</AccessibleTooltipButton>
</_default>
</div>
</LabelledToggleSwitch>
@ -84,37 +101,54 @@ exports[`<Notifications /> main notification switches renders only enable notifi
</Caption>
</span>
<_default
aria-label="Enable notifications for this account"
checked={false}
disabled={false}
onChange={[Function]}
title="Enable notifications for this account"
>
<AccessibleButton
<AccessibleTooltipButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable notifications for this account"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onClick={[Function]}
role="switch"
tabIndex={0}
title="Enable notifications for this account"
>
<div
<AccessibleButton
aria-checked={false}
aria-disabled={false}
aria-label="Enable notifications for this account"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
className="mx_ToggleSwitch mx_ToggleSwitch_enabled"
element="div"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
aria-checked={false}
aria-disabled={false}
aria-label="Enable notifications for this account"
className="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="switch"
tabIndex={0}
>
<div
className="mx_ToggleSwitch_ball"
/>
</div>
</AccessibleButton>
</AccessibleTooltipButton>
</_default>
</div>
</LabelledToggleSwitch>

View file

@ -19,6 +19,7 @@ import { fireEvent, render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import CurrentDeviceSection from '../../../../../src/components/views/settings/devices/CurrentDeviceSection';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
describe('<CurrentDeviceSection />', () => {
const deviceId = 'alices_device';
@ -26,10 +27,12 @@ describe('<CurrentDeviceSection />', () => {
const alicesVerifiedDevice = {
device_id: deviceId,
isVerified: false,
deviceType: DeviceType.Unknown,
};
const alicesUnverifiedDevice = {
device_id: deviceId,
isVerified: false,
deviceType: DeviceType.Unknown,
};
const defaultProps = {

View file

@ -19,6 +19,7 @@ import { fireEvent, render, RenderResult } from '@testing-library/react';
import { DeviceDetailHeading } from '../../../../../src/components/views/settings/devices/DeviceDetailHeading';
import { flushPromisesWithFakeTimers } from '../../../../test-utils';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
jest.useFakeTimers();
@ -27,6 +28,7 @@ describe('<DeviceDetailHeading />', () => {
device_id: '123',
display_name: 'My device',
isVerified: true,
deviceType: DeviceType.Unknown,
};
const defaultProps = {
device,

View file

@ -20,11 +20,13 @@ import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event';
import DeviceDetails from '../../../../../src/components/views/settings/devices/DeviceDetails';
import { mkPusher } from '../../../../test-utils/test-utils';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
describe('<DeviceDetails />', () => {
const baseDevice = {
device_id: 'my-device',
isVerified: false,
deviceType: DeviceType.Unknown,
};
const defaultProps = {
device: baseDevice,
@ -58,7 +60,10 @@ describe('<DeviceDetails />', () => {
display_name: 'My Device',
last_seen_ip: '123.456.789',
last_seen_ts: now - 60000000,
clientName: 'Element Web',
appName: 'Element Web',
client: 'Firefox 100',
deviceModel: 'Iphone X',
deviceOperatingSystem: 'Windows 95',
};
const { container } = render(getComponent({ device }));
expect(container).toMatchSnapshot();

View file

@ -19,12 +19,14 @@ import { render } from '@testing-library/react';
import { IMyDevice } from 'matrix-js-sdk/src/matrix';
import DeviceTile from '../../../../../src/components/views/settings/devices/DeviceTile';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
describe('<DeviceTile />', () => {
const defaultProps = {
device: {
device_id: '123',
isVerified: false,
deviceType: DeviceType.Unknown,
},
};
const getComponent = (props = {}) => (

View file

@ -1,44 +0,0 @@
/*
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.
*/
import { render } from '@testing-library/react';
import React from 'react';
import { DeviceType } from '../../../../../src/components/views/settings/devices/DeviceType';
describe('<DeviceType />', () => {
const defaultProps = {
isVerified: false,
isSelected: false,
};
const getComponent = (props = {}) =>
<DeviceType {...defaultProps} {...props} />;
it('renders an unverified device', () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders a verified device', () => {
const { container } = render(getComponent({ isVerified: true }));
expect(container).toMatchSnapshot();
});
it('renders correctly when selected', () => {
const { container } = render(getComponent({ isSelected: true }));
expect(container).toMatchSnapshot();
});
});

View file

@ -0,0 +1,74 @@
/*
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.
*/
import { render } from '@testing-library/react';
import React from 'react';
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
describe('<DeviceTypeIcon />', () => {
const defaultProps = {
isVerified: false,
isSelected: false,
};
const getComponent = (props = {}) =>
<DeviceTypeIcon {...defaultProps} {...props} />;
it('renders an unverified device', () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders a verified device', () => {
const { container } = render(getComponent({ isVerified: true }));
expect(container).toMatchSnapshot();
});
it('renders correctly when selected', () => {
const { container } = render(getComponent({ isSelected: true }));
expect(container).toMatchSnapshot();
});
it('renders an unknown device icon when no device type given', () => {
const { getByLabelText } = render(getComponent());
expect(getByLabelText('Unknown session type')).toBeTruthy();
});
it('renders a desktop device type', () => {
const deviceType = DeviceType.Desktop;
const { getByLabelText } = render(getComponent({ deviceType }));
expect(getByLabelText('Desktop session')).toBeTruthy();
});
it('renders a web device type', () => {
const deviceType = DeviceType.Web;
const { getByLabelText } = render(getComponent({ deviceType }));
expect(getByLabelText('Web session')).toBeTruthy();
});
it('renders a mobile device type', () => {
const deviceType = DeviceType.Mobile;
const { getByLabelText } = render(getComponent({ deviceType }));
expect(getByLabelText('Mobile session')).toBeTruthy();
});
it('renders an unknown device type', () => {
const deviceType = DeviceType.Unknown;
const { getByLabelText } = render(getComponent({ deviceType }));
expect(getByLabelText('Unknown session type')).toBeTruthy();
});
});

View file

@ -20,6 +20,7 @@ import { act, fireEvent, render } from '@testing-library/react';
import { FilteredDeviceList } from '../../../../../src/components/views/settings/devices/FilteredDeviceList';
import { DeviceSecurityVariation } from '../../../../../src/components/views/settings/devices/types';
import { flushPromises, mockPlatformPeg } from '../../../../test-utils';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
mockPlatformPeg();
@ -31,14 +32,26 @@ describe('<FilteredDeviceList />', () => {
last_seen_ip: '123.456.789',
display_name: 'My Device',
isVerified: true,
deviceType: DeviceType.Unknown,
};
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
const unverifiedNoMetadata = {
device_id: 'unverified-no-metadata',
isVerified: false,
deviceType: DeviceType.Unknown };
const verifiedNoMetadata = {
device_id: 'verified-no-metadata',
isVerified: true,
deviceType: DeviceType.Unknown };
const hundredDaysOld = {
device_id: '100-days-old',
isVerified: true,
last_seen_ts: Date.now() - (MS_DAY * 100),
deviceType: DeviceType.Unknown };
const hundredDaysOldUnverified = {
device_id: 'unverified-100-days-old',
isVerified: false,
last_seen_ts: Date.now() - (MS_DAY * 100),
deviceType: DeviceType.Unknown,
};
const defaultProps = {
onFilterChange: jest.fn(),

View file

@ -19,6 +19,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import SelectableDeviceTile from '../../../../../src/components/views/settings/devices/SelectableDeviceTile';
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
describe('<SelectableDeviceTile />', () => {
const device = {
@ -26,6 +27,7 @@ describe('<SelectableDeviceTile />', () => {
device_id: 'my-device',
last_seen_ip: '123.456.789',
isVerified: false,
deviceType: DeviceType.Unknown,
};
const defaultProps = {
onClick: jest.fn(),

View file

@ -151,16 +151,20 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -267,16 +271,20 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>

View file

@ -181,6 +181,18 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
my-device
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Client
</td>
<td
class="mxDeviceDetails_metadataValue"
>
Firefox 100
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
@ -233,6 +245,30 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
</tr>
</thead>
<tbody>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Model
</td>
<td
class="mxDeviceDetails_metadataValue"
>
Iphone X
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"
>
Operating system
</td>
<td
class="mxDeviceDetails_metadataValue"
>
Windows 95
</td>
</tr>
<tr>
<td
class="mxDeviceDetails_metadataLabel"

View file

@ -7,16 +7,20 @@ exports[`<DeviceTile /> renders a device with no metadata 1`] = `
data-testid="device-tile-123"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -58,16 +62,20 @@ exports[`<DeviceTile /> renders a verified device with no metadata 1`] = `
data-testid="device-tile-123"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -109,16 +117,20 @@ exports[`<DeviceTile /> renders display name with a tooltip 1`] = `
data-testid="device-tile-123"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -160,16 +172,20 @@ exports[`<DeviceTile /> separates metadata with a dot 1`] = `
data-testid="device-tile-123"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>

View file

@ -1,58 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<DeviceType /> renders a verified device 1`] = `
<div>
<div
class="mx_DeviceType"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
<div
aria-label="Verified"
class="mx_DeviceType_verificationIcon verified"
role="img"
/>
</div>
</div>
`;
exports[`<DeviceType /> renders an unverified device 1`] = `
<div>
<div
class="mx_DeviceType"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
role="img"
/>
</div>
</div>
`;
exports[`<DeviceType /> renders correctly when selected 1`] = `
<div>
<div
class="mx_DeviceType mx_DeviceType_selected"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
role="img"
/>
</div>
</div>
`;

View file

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<DeviceTypeIcon /> renders a verified device 1`] = `
<div>
<div
class="mx_DeviceTypeIcon"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Verified"
class="mx_DeviceTypeIcon_verificationIcon verified"
role="img"
/>
</div>
</div>
`;
exports[`<DeviceTypeIcon /> renders an unverified device 1`] = `
<div>
<div
class="mx_DeviceTypeIcon"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
</div>
`;
exports[`<DeviceTypeIcon /> renders correctly when selected 1`] = `
<div>
<div
class="mx_DeviceTypeIcon mx_DeviceTypeIcon_selected"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
</div>
`;

View file

@ -39,16 +39,20 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
data-testid="device-tile-my-device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>

View file

@ -20,18 +20,38 @@ import {
import {
DeviceSecurityVariation,
} from "../../../../../src/components/views/settings/devices/types";
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";
const MS_DAY = 86400000;
describe('filterDevicesBySecurityRecommendation()', () => {
const unverifiedNoMetadata = { device_id: 'unverified-no-metadata', isVerified: false };
const verifiedNoMetadata = { device_id: 'verified-no-metadata', isVerified: true };
const hundredDaysOld = { device_id: '100-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 100) };
const unverifiedNoMetadata = {
device_id: 'unverified-no-metadata',
isVerified: false,
deviceType: DeviceType.Unknown,
};
const verifiedNoMetadata = {
device_id: 'verified-no-metadata',
isVerified: true,
deviceType: DeviceType.Unknown,
};
const hundredDaysOld = {
device_id: '100-days-old',
isVerified: true,
last_seen_ts: Date.now() - (MS_DAY * 100),
deviceType: DeviceType.Unknown,
};
const hundredDaysOldUnverified = {
device_id: 'unverified-100-days-old',
isVerified: false,
last_seen_ts: Date.now() - (MS_DAY * 100),
deviceType: DeviceType.Unknown,
};
const fiftyDaysOld = {
device_id: '50-days-old',
isVerified: true,
last_seen_ts: Date.now() - (MS_DAY * 50),
deviceType: DeviceType.Unknown,
};
const fiftyDaysOld = { device_id: '50-days-old', isVerified: true, last_seen_ts: Date.now() - (MS_DAY * 50) };
const devices = [
unverifiedNoMetadata,

View file

@ -16,30 +16,35 @@ limitations under the License.
import React from "react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { EventType } from "matrix-js-sdk/src/@types/event";
import RolesRoomSettingsTab from "../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
import { mkStubRoom, stubClient } from "../../../../../test-utils";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import { VoiceBroadcastInfoEventType } from "../../../../../../src/voice-broadcast";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
import { ElementCall } from "../../../../../../src/models/Call";
describe("RolesRoomSettingsTab", () => {
const roomId = "!room:example.com";
let rolesRoomSettingsTab: RenderResult;
let cli: MatrixClient;
const renderTab = (): RenderResult => {
return render(<RolesRoomSettingsTab roomId={roomId} />);
};
const getVoiceBroadcastsSelect = () => {
return rolesRoomSettingsTab.container.querySelector("select[label='Voice broadcasts']");
return renderTab().container.querySelector("select[label='Voice broadcasts']");
};
const getVoiceBroadcastsSelectedOption = () => {
return rolesRoomSettingsTab.container.querySelector("select[label='Voice broadcasts'] option:checked");
return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked");
};
beforeEach(() => {
stubClient();
cli = MatrixClientPeg.get();
rolesRoomSettingsTab = render(<RolesRoomSettingsTab roomId={roomId} />);
mkStubRoom(roomId, "test room", cli);
});
@ -66,4 +71,96 @@ describe("RolesRoomSettingsTab", () => {
);
});
});
describe("Element Call", () => {
const setGroupCallsEnabled = (val: boolean): void => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
if (name === "feature_group_calls") return val;
});
};
const getStartCallSelect = (tab: RenderResult) => {
return tab.container.querySelector("select[label='Start Element Call calls']");
};
const getStartCallSelectedOption = (tab: RenderResult) => {
return tab.container.querySelector("select[label='Start Element Call calls'] option:checked");
};
const getJoinCallSelect = (tab: RenderResult) => {
return tab.container.querySelector("select[label='Join Element Call calls']");
};
const getJoinCallSelectedOption = (tab: RenderResult) => {
return tab.container.querySelector("select[label='Join Element Call calls'] option:checked");
};
describe("Element Call enabled", () => {
beforeEach(() => {
setGroupCallsEnabled(true);
});
describe("Join Element calls", () => {
it("defaults to moderator for joining calls", () => {
expect(getJoinCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
});
it("can change joining calls power level", () => {
const tab = renderTab();
fireEvent.change(getJoinCallSelect(tab), {
target: { value: 0 },
});
expect(getJoinCallSelectedOption(tab)?.textContent).toBe("Default");
expect(cli.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPowerLevels,
{
events: {
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
},
},
);
});
});
describe("Start Element calls", () => {
it("defaults to moderator for starting calls", () => {
expect(getStartCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
});
it("can change starting calls power level", () => {
const tab = renderTab();
fireEvent.change(getStartCallSelect(tab), {
target: { value: 0 },
});
expect(getStartCallSelectedOption(tab)?.textContent).toBe("Default");
expect(cli.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPowerLevels,
{
events: {
[ElementCall.CALL_EVENT_TYPE.name]: 0,
},
},
);
});
});
});
it("hides when group calls disabled", () => {
setGroupCallsEnabled(false);
const tab = renderTab();
expect(getStartCallSelect(tab)).toBeFalsy();
expect(getStartCallSelectedOption(tab)).toBeFalsy();
expect(getJoinCallSelect(tab)).toBeFalsy();
expect(getJoinCallSelectedOption(tab)).toBeFalsy();
});
});
});

View file

@ -0,0 +1,141 @@
/*
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.
*/
import React from "react";
import { fireEvent, render, RenderResult, waitFor } from "@testing-library/react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { mkStubRoom, stubClient } from "../../../../../test-utils";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import { VoipRoomSettingsTab } from "../../../../../../src/components/views/settings/tabs/room/VoipRoomSettingsTab";
import { ElementCall } from "../../../../../../src/models/Call";
describe("RolesRoomSettingsTab", () => {
const roomId = "!room:example.com";
let cli: MatrixClient;
let room: Room;
const renderTab = (): RenderResult => {
return render(<VoipRoomSettingsTab roomId={roomId} />);
};
beforeEach(() => {
stubClient();
cli = MatrixClientPeg.get();
room = mkStubRoom(roomId, "test room", cli);
jest.spyOn(cli, "sendStateEvent");
jest.spyOn(cli, "getRoom").mockReturnValue(room);
});
describe("Element Call", () => {
const mockPowerLevels = (events): void => {
jest.spyOn(room.currentState, "getStateEvents").mockReturnValue({
getContent: () => ({
events,
}),
} as unknown as MatrixEvent);
};
const getElementCallSwitch = (tab: RenderResult): HTMLElement => {
return tab.container.querySelector("[data-testid='element-call-switch']");
};
describe("correct state", () => {
it("shows enabled when call member power level is 0", () => {
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
const tab = renderTab();
expect(getElementCallSwitch(tab).querySelector("[aria-checked='true']")).toBeTruthy();
});
it.each([1, 50, 100])("shows disabled when call member power level is 0", (level: number) => {
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: level });
const tab = renderTab();
expect(getElementCallSwitch(tab).querySelector("[aria-checked='false']")).toBeTruthy();
});
});
describe("enabling/disabling", () => {
describe("enabling Element calls", () => {
beforeEach(() => {
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 100 });
});
it("enables Element calls in public room", async () => {
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId,
EventType.RoomPowerLevels,
expect.objectContaining({
events: {
[ElementCall.CALL_EVENT_TYPE.name]: 50,
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
},
}),
));
});
it("enables Element calls in private room", async () => {
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId,
EventType.RoomPowerLevels,
expect.objectContaining({
events: {
[ElementCall.CALL_EVENT_TYPE.name]: 0,
[ElementCall.MEMBER_EVENT_TYPE.name]: 0,
},
}),
));
});
});
it("disables Element calls", async () => {
mockPowerLevels({ [ElementCall.MEMBER_EVENT_TYPE.name]: 0 });
const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch"));
await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId,
EventType.RoomPowerLevels,
expect.objectContaining({
events: {
[ElementCall.CALL_EVENT_TYPE.name]: 100,
[ElementCall.MEMBER_EVENT_TYPE.name]: 100,
},
}),
));
});
});
});
});

View file

@ -0,0 +1,121 @@
/*
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.
*/
import React from "react";
import { fireEvent, render, RenderResult, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import PreferencesUserSettingsTab from
"../../../../../../src/components/views/settings/tabs/user/PreferencesUserSettingsTab";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import { mockPlatformPeg, stubClient } from "../../../../../test-utils";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
describe("PreferencesUserSettingsTab", () => {
beforeEach(() => {
mockPlatformPeg();
});
const renderTab = (): RenderResult => {
return render(<PreferencesUserSettingsTab closeSettingsFn={() => {}} />);
};
describe("send read receipts", () => {
beforeEach(() => {
stubClient();
jest.spyOn(SettingsStore, "setValue");
jest.spyOn(window, "matchMedia").mockReturnValue({ matches: false } as MediaQueryList);
});
afterEach(() => {
jest.resetAllMocks();
});
const getToggle = () => renderTab().getByRole("switch", { name: "Send read receipts" });
const mockIsVersionSupported = (val: boolean) => {
const client = MatrixClientPeg.get();
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
if (version === "v1.4") return val;
});
};
const mockGetValue = (val: boolean) => {
const copyOfGetValueAt = SettingsStore.getValueAt;
SettingsStore.getValueAt = (level: SettingLevel, name: string, roomId?: string, isExplicit?: boolean) => {
if (name === "sendReadReceipts") return val;
return copyOfGetValueAt(level, name, roomId, isExplicit);
};
};
const expectSetValueToHaveBeenCalled = (
name: string,
roomId: string,
level: SettingLevel,
value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
describe("with server support", () => {
beforeEach(() => {
mockIsVersionSupported(true);
});
it("can be enabled", async () => {
mockGetValue(false);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true);
});
it("can be disabled", async () => {
mockGetValue(true);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, false);
});
});
describe("without server support", () => {
beforeEach(() => {
mockIsVersionSupported(false);
});
it("can be enabled", async () => {
mockGetValue(false);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true);
});
it("cannot be disabled", async () => {
mockGetValue(true);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "true"));
fireEvent.click(toggle);
expect(SettingsStore.setValue).not.toHaveBeenCalled();
});
});
});
});

View file

@ -44,7 +44,7 @@ import Modal from '../../../../../../src/Modal';
import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
import {
DeviceSecurityVariation,
DeviceWithVerification,
ExtendedDevice,
} from '../../../../../../src/components/views/settings/devices/types';
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
@ -104,7 +104,7 @@ describe('<SessionManagerTab />', () => {
const toggleDeviceDetails = (
getByTestId: ReturnType<typeof render>['getByTestId'],
deviceId: DeviceWithVerification['device_id'],
deviceId: ExtendedDevice['device_id'],
) => {
// open device detail
const tile = getByTestId(`device-tile-${deviceId}`);
@ -114,7 +114,7 @@ describe('<SessionManagerTab />', () => {
const toggleDeviceSelection = (
getByTestId: ReturnType<typeof render>['getByTestId'],
deviceId: DeviceWithVerification['device_id'],
deviceId: ExtendedDevice['device_id'],
) => {
const checkbox = getByTestId(`device-tile-checkbox-${deviceId}`);
fireEvent.click(checkbox);
@ -135,7 +135,7 @@ describe('<SessionManagerTab />', () => {
const isDeviceSelected = (
getByTestId: ReturnType<typeof render>['getByTestId'],
deviceId: DeviceWithVerification['device_id'],
deviceId: ExtendedDevice['device_id'],
): boolean => !!(getByTestId(`device-tile-checkbox-${deviceId}`) as HTMLInputElement).checked;
const isSelectAllChecked = (

View file

@ -94,16 +94,20 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Verified"
class="mx_DeviceType_verificationIcon verified"
class="mx_DeviceTypeIcon_verificationIcon verified"
role="img"
/>
</div>
@ -196,16 +200,20 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceType_verificationIcon unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
@ -298,16 +306,20 @@ exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceType"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown device type"
class="mx_DeviceType_deviceIcon"
role="img"
/>
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Verified"
class="mx_DeviceType_verificationIcon verified"
class="mx_DeviceTypeIcon_verificationIcon verified"
role="img"
/>
</div>

View file

@ -22,6 +22,7 @@ import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { Widget } from "matrix-widget-api";
import "@testing-library/jest-dom";
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
import type { ClientWidgetApi } from "matrix-widget-api";
@ -38,6 +39,7 @@ import { CallView as _CallView } from "../../../../src/components/views/voip/Cal
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import { CallStore } from "../../../../src/stores/CallStore";
import { Call, ConnectionState } from "../../../../src/models/Call";
import SdkConfig from "../../../../src/SdkConfig";
const CallView = wrapInMatrixClientContext(_CallView);
@ -90,7 +92,7 @@ describe("CallLobby", () => {
beforeEach(() => {
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.get(room.roomId);
const maybeCall = CallStore.instance.getCall(room.roomId);
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
call = maybeCall;
@ -163,6 +165,23 @@ describe("CallLobby", () => {
fireEvent.click(screen.getByRole("button", { name: "Join" }));
await waitFor(() => expect(connectSpy).toHaveBeenCalled(), { interval: 1 });
});
it("disables join button when the participant limit has been exceeded", async () => {
const bob = mkRoomMember(room.roomId, "@bob:example.org");
const carol = mkRoomMember(room.roomId, "@carol:example.org");
SdkConfig.put({
"element_call": { participant_limit: 2, url: "", use_exclusively: false, brand: "Element Call" },
});
call.participants = new Set([bob, carol]);
await renderView();
const connectSpy = jest.spyOn(call, "connect");
const joinButton = screen.getByRole("button", { name: "Join" });
expect(joinButton).toHaveAttribute("aria-disabled", "true");
fireEvent.click(joinButton);
await waitFor(() => expect(connectSpy).not.toHaveBeenCalled(), { interval: 1 });
});
});
describe("without an existing call", () => {
@ -171,8 +190,8 @@ describe("CallLobby", () => {
expect(Call.get(room)).toBeNull();
fireEvent.click(screen.getByRole("button", { name: "Join" }));
await waitFor(() => expect(CallStore.instance.get(room.roomId)).not.toBeNull());
const call = CallStore.instance.get(room.roomId)!;
await waitFor(() => expect(CallStore.instance.getCall(room.roomId)).not.toBeNull());
const call = CallStore.instance.getCall(room.roomId)!;
const widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {

View file

@ -0,0 +1,175 @@
/*
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.
*/
import React from "react";
import { mocked, Mocked } from "jest-mock";
import { screen, render, act, cleanup, fireEvent, waitFor } from "@testing-library/react";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { Widget, ClientWidgetApi } from "matrix-widget-api";
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
import {
useMockedCalls,
MockedCall,
mkRoomMember,
stubClient,
setupAsyncStoreWithClient,
resetAsyncStoreWithClient,
wrapInMatrixClientContext,
} from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { CallStore } from "../../../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import UnwrappedPipView from "../../../../src/components/views/voip/PipView";
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
const PipView = wrapInMatrixClientContext(UnwrappedPipView);
describe("PipView", () => {
useMockedCalls();
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
let client: Mocked<MatrixClient>;
let room: Room;
let alice: RoomMember;
beforeEach(async () => {
stubClient();
client = mocked(MatrixClientPeg.get());
DMRoomMap.makeShared();
room = new Room("!1:example.org", client, "@alice:example.org", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRooms.mockReturnValue([room]);
alice = mkRoomMember(room.roomId, "@alice:example.org");
jest.spyOn(room, "getMember").mockImplementation(userId => userId === alice.userId ? alice : null);
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
));
});
afterEach(async () => {
cleanup();
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(resetAsyncStoreWithClient));
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
jest.restoreAllMocks();
});
const renderPip = () => { render(<PipView />); };
const viewRoom = (roomId: string) =>
defaultDispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: roomId,
metricsTrigger: undefined,
}, true);
const withCall = async (fn: () => Promise<void>): Promise<void> => {
MockedCall.create(room, "1");
const call = CallStore.instance.getCall(room.roomId);
if (!(call instanceof MockedCall)) throw new Error("Failed to create call");
const widget = new Widget(call.widget);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);
await act(async () => {
await call.connect();
ActiveWidgetStore.instance.setWidgetPersistence(widget.id, room.roomId, true);
});
await fn();
cleanup();
call.destroy();
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
};
const withWidget = (fn: () => void): void => {
act(() => ActiveWidgetStore.instance.setWidgetPersistence("1", room.roomId, true));
fn();
cleanup();
ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId);
};
it("hides if there's no content", () => {
renderPip();
expect(screen.queryByRole("complementary")).toBeNull();
});
it("shows an active call with a maximise button", async () => {
renderPip();
await withCall(async () => {
screen.getByRole("complementary");
screen.getByText(room.roomId);
expect(screen.queryByRole("button", { name: "Pin" })).toBeNull();
expect(screen.queryByRole("button", { name: /return/i })).toBeNull();
// The maximise button should jump to the call
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Fill screen" }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
}));
defaultDispatcher.unregister(dispatcherRef);
});
});
it("shows a persistent widget with pin and maximise buttons when viewing the room", () => {
viewRoom(room.roomId);
renderPip();
withWidget(() => {
screen.getByRole("complementary");
screen.getByText(room.roomId);
screen.getByRole("button", { name: "Pin" });
screen.getByRole("button", { name: "Fill screen" });
expect(screen.queryByRole("button", { name: /return/i })).toBeNull();
});
});
it("shows a persistent widget with a return button when not viewing the room", () => {
viewRoom("!2:example.org");
renderPip();
withWidget(() => {
screen.getByRole("complementary");
screen.getByText(room.roomId);
expect(screen.queryByRole("button", { name: "Pin" })).toBeNull();
expect(screen.queryByRole("button", { name: "Fill screen" })).toBeNull();
screen.getByRole("button", { name: /return/i });
});
});
});