Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Community admins can delete user messages #8892

Merged
merged 2 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified test/ui-test/fixtures/community_members/accounts.sql-shm
Binary file not shown.
Binary file modified test/ui-test/fixtures/community_members/accounts.sql-wal
Binary file not shown.
6 changes: 3 additions & 3 deletions test/ui-test/src/screens/StatusMainScreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class MainScreenComponents(Enum):
SPLASH_SCREEN = "splashScreen"
TOOLBAR_BACK_BUTTON = "main_toolBar_back_button"
LEAVE_CHAT_MENUITEM = "leaveChatMenuItem"
EMPTY_CHAT_PANEL_IMAGE = "mainWindow_emptyChatPanelImage"
CONTACTS_COLUMN_MESSAGES_HEADLINE = "mainWindow_ContactsColumn_Messages_Headline"

class ProfilePopup(Enum):
USER_IMAGE = "ProfileHeader_userImage"
Expand All @@ -58,13 +58,13 @@ class ChatNamePopUp(Enum):
class StatusMainScreen:

def __init__(self):
verify_screen(MainScreenComponents.EMPTY_CHAT_PANEL_IMAGE.value)
verify_screen(MainScreenComponents.CONTACTS_COLUMN_MESSAGES_HEADLINE.value)

# Main screen is ready to interact with it (Splash screen animation not present and no banners on top of the screen)
def is_ready(self):
self.wait_for_splash_animation_ends()
self.close_banners()
verify(is_displayed(MainScreenComponents.EMPTY_CHAT_PANEL_IMAGE.value), "Verifying if empty chat panel image is displayed")
verify(is_displayed(MainScreenComponents.CONTACTS_COLUMN_MESSAGES_HEADLINE.value), "Verifying if the Messages headline is displayed")

def wait_for_splash_animation_ends(self, timeoutMSec: int = 10000):
start = time.time()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
statusDesktop_mainWindow = {"name": "mainWindow", "type": "StatusWindow", "visible": True}
statusDesktop_mainWindow_overlay = {"container": statusDesktop_mainWindow, "type": "Overlay", "unnamed": 1, "visible": True}
mainWindow_navBarListView_ListView = {"container": statusDesktop_mainWindow, "objectName": "statusMainNavBarListView", "type": "ListView", "visible": True}
mainWindow_communityNavBarListView_ListView = {"container": statusDesktop_mainWindow, "objectName": "statusCommunityMainNavBarListView", "type": "ListView", "visible": True}
chatView_log = {"container": statusDesktop_mainWindow, "objectName": "chatLogView", "type": "StatusListView", "visible": True}
secureSeedPhrase_Banner = {"container": statusDesktop_mainWindow, "objectName": "secureYourSeedPhraseBanner", "type": "ModuleWarning"}
connectionInfo_Banner = {"container": statusDesktop_mainWindow, "objectName": "connectionInfoBanner", "type": "ModuleWarning"}
Expand All @@ -17,6 +18,7 @@
mainWindow_StatusToolBar = {"container": statusDesktop_mainWindow, "objectName": "statusToolBar", "type": "StatusToolBar", "visible": True}
main_toolBar_back_button = {"container": mainWindow_StatusToolBar, "objectName": "toolBarBackButton", "type": "StatusFlatButton", "visible": True}
mainWindow_emptyChatPanelImage = {"container": statusDesktop_mainWindow, "objectName": "emptyChatPanelImage", "type": "Image", "visible": True}
mainWindow_ContactsColumn_Messages_Headline = {"container": statusDesktop_mainWindow, "objectName": "ContactsColumnView_MessagesHeadline", "type": "StatusNavigationPanelHeadline"}


# main right panel
Expand All @@ -37,7 +39,7 @@
mainWindow_statusChatNavBarListView_ListView = {"container": statusDesktop_mainWindow, "objectName": "statusChatNavBarListView", "type": "ListView", "visible": True}
navBarListView_Chat_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_statusChatNavBarListView_ListView, "objectName": "Messages-navbar", "type": "StatusNavBarTabButton", "visible": True}
mainWindow_public_chat_icon_StatusIcon = {"container": statusDesktop_mainWindow, "objectName": "public-chat-icon", "source": "qrc:/StatusQ/src/assets/img/icons/public-chat.svg", "type": "StatusIcon", "visible": True}
chatList_Repeater = {"container": statusDesktop_mainWindow, "objectName": "chatListItems", "type": "Repeater"}
chatList_Repeater = {"container": statusDesktop_mainWindow, "objectName": "chatListItems", "type": "Repeater", "visible": True}
chatList = {"container": statusDesktop_mainWindow, "objectName": "ContactsColumnView_chatList", "type": "StatusChatList"}
mainWindow_startChat = {"checkable": True, "container": statusDesktop_mainWindow, "objectName": "startChatButton", "type": "StatusIconTabButton"}
join_public_chat_StatusMenuItem = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "enabled": True, "text": "Join public chat", "type": "StatusMenuItem", "unnamed": 1, "visible": True}
Expand All @@ -46,6 +48,7 @@
chatInfoButton_Pin_Text = {"container": chatView_StatusChatInfoButton, "objectName": "StatusChatInfo_pinText", "type": "StatusBaseText", "visible": True}
joinPublicChat_input = {"container": statusDesktop_mainWindow_overlay, "objectName": "joinPublicChannelInput", "type": "TextEdit", "visible": True}
startChat_Btn = {"container": statusDesktop_mainWindow_overlay, "objectName": "startChatButton", "type": "StatusButton"}
chatButtonsPanelConfirmDeleteMessageButton_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatButtonsPanelConfirmDeleteMessageButton", "type": "StatusButton"}

# My Profile Popup
ProfileHeader_userImage = {"container": statusDesktop_mainWindow_overlay, "objectName": "ProfileHeader_userImage", "type": "UserImage", "visible": True}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-

# This file contains hook functions to run as the .feature file is executed.
#
# A common use-case is to use the OnScenarioStart/OnScenarioEnd hooks to
# start and stop an AUT, e.g.
#
# @OnScenarioStart
# def hook(context):
# startApplication("addressbook")
#
# @OnScenarioEnd
# def hook(context):
# currentApplicationContext().detach()
#
# See the section 'Performing Actions During Test Execution Via Hooks' in the Squish
# manual for a complete reference of the available API.

# Detach (i.e. potentially terminate) all AUTs at the end of a scenario
@OnScenarioEnd
def hook(context):
for ctx in applicationContextList():
ctx.detach()

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
navBarListView_Communities_Portal_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_navBarListView_ListView, "objectName": "Communities Portal-navbar", "type": "StatusNavBarTabButton", "visible": True}
mainWindow_communitiesPortalLayoutContainer_CommunitiesPortalLayout = {"container": statusDesktop_mainWindow, "objectName": "communitiesPortalLayout", "type": "CommunitiesPortalLayout"}
communitiesPortalLayoutContainer_createCommunityButton_StatusButton = {"container": mainWindow_communitiesPortalLayoutContainer_CommunitiesPortalLayout, "objectName": "createCommunityButton", "type": "StatusButton", "visible": True}
navBarListView_All_Community_Buttons = {"checkable": True, "container": mainWindow_navBarListView_ListView, "objectName": "CommunityNavBarButton", "type": "StatusNavBarTabButton"}
navBarListView_All_Community_Buttons = {"checkable": True, "container": mainWindow_communityNavBarListView_ListView, "objectName": "CommunityNavBarButton", "type": "StatusNavBarTabButton"}

# Create community popup:
createCommunityNameInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "createCommunityNameInput", "type": "TextEdit", "visible": True}
Expand Down
31 changes: 31 additions & 0 deletions test/ui-test/testSuites/suite_communities/shared/steps/steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-

# A quick introduction to implementing scripts for BDD tests:
#
# This file contains snippets of script code to be executed as the .feature
# file is processed. See the section 'Behaviour Driven Testing' in the 'API
# Reference Manual' chapter of the Squish manual for a comprehensive reference.
#
# The decorators Given/When/Then/Step can be used to associate a script snippet
# with a pattern which is matched against the steps being executed. Optional
# table/multi-line string arguments of the step are passed via a mandatory
# 'context' parameter:
#
# @When("I enter the text")
# def whenTextEntered(context):
# <code here>
#
# The pattern is a plain string without the leading keyword, but a couple of
# placeholders including |any|, |word| and |integer| are supported which can be
# used to extract arbitrary, alphanumeric and integer values resp. from the
# pattern; the extracted values are passed as additional arguments:
#
# @Then("I get |integer| different names")
# def namesReceived(context, numNames):
# <code here>
#
# Instead of using a string with placeholders, a regular expression can be
# specified. In that case, make sure to set the (optional) 'regexp' argument
# to True.

import names
2 changes: 1 addition & 1 deletion test/ui-test/testSuites/suite_communities/suite.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ AUT=nim_status_client
ENVVARS=envvars
LANGUAGE=Python
OBJECTMAPSTYLE=script
TEST_CASES=tst_communityFlows tst_searchFlows tst_communityMessageFlows tst_communityMemberFlows
TEST_CASES=tst_communityFlows tst_searchFlows tst_communityMessageFlows tst_communityMemberFlows tst_communityAdminFlows
VERSION=3
WRAPPERS=Qt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#******************************************************************************
# Status.im
#*****************************************************************************/
#/**
# * \file test.feature
# *
# * \test Status Desktop - Community Member Flows
# * \date August 2022
# **
# *****************************************************************************/

Feature: Status Desktop community admin features

As an admin I want to interact in a community

@relyon-mailserver
# TODO we need the mailserver to get the message we want to delete
Scenario: Admin can delete another member's message
# User 1 Bobby sends a message
Given the user starts the application with a specific data folder "../../../fixtures/community_members"
When the user "Bobby" logs in with password "TesTEr16843/!@00"
Then the user lands on the signed in app
When the user opens the community named "MyFriends"
Then the user lands on the community named "MyFriends"
When the user switches to "general" chat
# Buffer message so that we are sure that once deleted, the last message will not be and old one
And the user sends a chat message "Wholesome message"
And the user sends a chat message "I sure hope no admin will delete this message"
Then the last chat message contains "I sure hope no admin will delete this message"

# User 2 Alice (admin) logs in
Given the user restarts the app
And the user "Alive" logs in with password "TesTEr16843/!@00"
Then the user lands on the signed in app
When the user opens the community named "MyFriends"
Then the user lands on the community named "MyFriends"
And the last chat message contains "I sure hope no admin will delete this message"

# Deleting the message
When the user deletes the message at index 0
Then the last message displayed is not "I sure hope no admin will delete this message"


Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
source(findFile('scripts', 'python/bdd.py'))

setupHooks('../../global_shared/scripts/bdd_hooks.py')
collectStepDefinitions('./steps', '../shared/steps/', '../../global_shared/steps/', '../../suite_onboarding/shared/steps/', '../../suite_messaging/shared/steps/')


def main():
testSettings.throwOnFailure = True
runFeatureFile('test.feature')
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
chatView_lastChatText_Text = {"container": chatView_chatLogView_lastMsg_MessageView, "type": "TextEdit", "objectName": "StatusTextMessage_chatText", "visible": True}
chatView_editMessageInputComponent = {"container": statusDesktop_mainWindow, "objectName": "editMessageInput", "type": "StatusChatInput", "visible": True}
chatView_editMessageInputTextArea = {"container": chatView_editMessageInputComponent, "objectName": "messageInputField", "type": "TextArea", "visible": True}
chatButtonsPanelConfirmDeleteMessageButton_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatButtonsPanelConfirmDeleteMessageButton", "type": "StatusButton"}
mark_as_Read_StatusMenuItem = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatMarkAsReadMenuItem", "type": "StatusMenuItem", "visible": True}
chat_Input_Stickers_Button = {"container": statusDesktop_mainWindow, "objectName": "statusChatInputStickersButton", "type": "StatusFlatRoundButton", "visible": True}
chatView_SuggestionBoxPanel ={"container": statusDesktop_mainWindow, "objectName": "suggestionsBox", "type": "SuggestionBoxPanel"}
Expand Down
1 change: 1 addition & 0 deletions ui/StatusQ/src/StatusQ/Layout/StatusAppNavBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Rectangle {

ListView {
id: communityItemsListView
objectName: "statusCommunityMainNavBarListView"

Layout.fillWidth: true
Layout.fillHeight: true
Expand Down
2 changes: 1 addition & 1 deletion ui/app/AppLayouts/Chat/views/ChatMessagesView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Item {
return
}

if (chatDetails.active && chatDetails.hasUnreadMessages && !messageStore.messageSearchOngoing) {
if (chatDetails && chatDetails.active && chatDetails.hasUnreadMessages && !messageStore.messageSearchOngoing) {
chatContentModule.markAllMessagesRead()
}
}
Expand Down
1 change: 1 addition & 0 deletions ui/app/AppLayouts/Chat/views/ContactsColumnView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Item {
Layout.fillWidth: true

StatusNavigationPanelHeadline {
objectName: "ContactsColumnView_MessagesHeadline"
Layout.alignment: Qt.AlignVCenter
text: qsTr("Messages")
}
Expand Down
2 changes: 1 addition & 1 deletion ui/imports/shared/views/chat/MessageContextMenuView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ StatusMenu {

StatusAction {
id: deleteMessageAction
enabled: root.isMyMessage &&
enabled: (root.isMyMessage || root.amIChatAdmin) &&
!root.isProfile &&
!root.isEmoji &&
!root.pinnedPopup &&
Expand Down
13 changes: 6 additions & 7 deletions ui/imports/shared/views/chat/MessageView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Loader {
property bool senderIsEnsVerified: false
property string senderIcon: ""
property bool amISender: false
property bool amIChatAdmin: messageStore && messageStore.amIChatAdmin()
property bool senderIsAdded: false
property int senderTrustStatus: Constants.trustStatus.unknown
property string messageText: ""
Expand Down Expand Up @@ -133,7 +134,7 @@ Loader {
}

messageContextMenu.myPublicKey = userProfile.pubKey
messageContextMenu.amIChatAdmin = messageStore.amIChatAdmin()
messageContextMenu.amIChatAdmin = root.amIChatAdmin
messageContextMenu.pinMessageAllowedForMembers = messageStore.pinMessageAllowedForMembers()
messageContextMenu.chatType = messageStore.getChatType()

Expand Down Expand Up @@ -333,7 +334,7 @@ Loader {
chatType: root.messageStore.getChatType()
chatColor: root.messageStore.getChatColor()
chatEmoji: root.channelEmoji
amIChatAdmin: root.messageStore.amIChatAdmin()
amIChatAdmin: root.amIChatAdmin
chatIcon: {
if ((root.messageStore.getChatType() === Constants.chatType.privateGroupChat) &&
root.messageStore.getChatIcon() !== "") {
Expand Down Expand Up @@ -838,12 +839,11 @@ Loader {
return false

const chatType = root.messageStore.getChatType();
const amIChatAdmin = root.messageStore.amIChatAdmin();
const pinMessageAllowedForMembers = root.messageStore.pinMessageAllowedForMembers()

return chatType === Constants.chatType.oneToOne ||
chatType === Constants.chatType.privateGroupChat && amIChatAdmin ||
chatType === Constants.chatType.communityChat && (amIChatAdmin || pinMessageAllowedForMembers);
chatType === Constants.chatType.privateGroupChat && root.amIChatAdmin ||
chatType === Constants.chatType.communityChat && (root.amIChatAdmin || pinMessageAllowedForMembers);

}
sourceComponent: StatusFlatRoundButton {
Expand Down Expand Up @@ -884,9 +884,8 @@ Loader {
return false;
if (!root.messageStore)
return false;
const isMyMessage = senderId !== "" && senderId === userProfile.pubKey;
const chatType = root.messageStore.getChatType();
return isMyMessage &&
return (root.amISender || root.amIChatAdmin) &&
(messageContentType === Constants.messageContentType.messageType ||
messageContentType === Constants.messageContentType.stickerType ||
messageContentType === Constants.messageContentType.emojiType ||
Expand Down