diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift index 5db52970c..ece131c45 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsDecorator.swift @@ -10,7 +10,7 @@ import FlowCryptUI import UIKit extension ThreadMessageInfoCellNode.Input { - init(threadMessage: ThreadDetailsViewController.Input) { + init(threadMessage: ThreadDetailsViewController.Input, index: Int) { let sender = threadMessage.rawMessage.sender ?? "message_unknown_sender".localized let recipientPrefix = "to".localized let recipientsList = threadMessage.rawMessage @@ -40,7 +40,8 @@ extension ThreadMessageInfoCellNode.Input { date: .text(from: date, style: style, color: dateColor), isExpanded: threadMessage.isExpanded, shouldShowRecipientsList: threadMessage.shouldShowRecipientsList, - buttonColor: .colorFor(darkStyle: .white, lightStyle: .main) + buttonColor: .colorFor(darkStyle: .white, lightStyle: .main), + index: index ) } } diff --git a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift index ef230f42a..8406617b2 100644 --- a/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift +++ b/FlowCrypt/Controllers/Threads/ThreadDetailsViewController.swift @@ -449,9 +449,11 @@ extension ThreadDetailsViewController { } } - private func retryVerifyingSignatureWithRemotelyFetchedKeys(message: Message, - folder: String, - indexPath: IndexPath) { + private func retryVerifyingSignatureWithRemotelyFetchedKeys( + message: Message, + folder: String, + indexPath: IndexPath + ) { Task { do { let processedMessage = try await messageService.getAndProcessMessage( @@ -561,11 +563,12 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { return MessageSubjectNode(subject.attributed(.medium(18))) } - let message = self.input[indexPath.section - 1] + let messageIndex = indexPath.section - 1 + let message = self.input[messageIndex] if indexPath.row == 0 { return ThreadMessageInfoCellNode( - input: .init(threadMessage: message), + input: .init(threadMessage: message, index: messageIndex), onReplyTap: { [weak self] _ in self?.handleReplyTap(at: indexPath) }, onMenuTap: { [weak self] _ in self?.handleMenuTap(at: indexPath) }, onRecipientsTap: { [weak self] _ in self?.handleRecipientsTap(at: indexPath) } @@ -577,7 +580,7 @@ extension ThreadDetailsViewController: ASTableDelegate, ASTableDataSource { } guard indexPath.row > 1 else { - return MessageTextSubjectNode(processedMessage.attributedMessage) + return MessageTextSubjectNode(processedMessage.attributedMessage, index: messageIndex) } let attachmentIndex = indexPath.row - 2 diff --git a/FlowCryptUI/Cell Nodes/MessageTextSubjectNode.swift b/FlowCryptUI/Cell Nodes/MessageTextSubjectNode.swift index 7ffd1694b..f74ff8c6a 100644 --- a/FlowCryptUI/Cell Nodes/MessageTextSubjectNode.swift +++ b/FlowCryptUI/Cell Nodes/MessageTextSubjectNode.swift @@ -11,9 +11,13 @@ import AsyncDisplayKit public final class MessageTextSubjectNode: CellNode { private let textNode = ASEditableTextNode() - public init(_ text: NSAttributedString?) { + public init(_ text: NSAttributedString?, index: Int) { super.init() textNode.attributedText = text + textNode.isAccessibilityElement = true + textNode.accessibilityIdentifier = "aid-message-\(index)" + textNode.accessibilityValue = text?.string + DispatchQueue.main.async { self.textNode.textView.isSelectable = true self.textNode.textView.isEditable = false diff --git a/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift b/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift index 8d98b6b75..f47642342 100644 --- a/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift +++ b/FlowCryptUI/Cell Nodes/ThreadMessageInfoCellNode.swift @@ -23,6 +23,7 @@ public final class ThreadMessageInfoCellNode: CellNode { public let isExpanded: Bool public let shouldShowRecipientsList: Bool public let buttonColor: UIColor + public let index: Int public init( encryptionBadge: BadgeNode.Input, @@ -35,7 +36,8 @@ public final class ThreadMessageInfoCellNode: CellNode { date: NSAttributedString, isExpanded: Bool, shouldShowRecipientsList: Bool, - buttonColor: UIColor + buttonColor: UIColor, + index: Int ) { self.encryptionBadge = encryptionBadge self.signatureBadge = signatureBadge @@ -48,6 +50,7 @@ public final class ThreadMessageInfoCellNode: CellNode { self.isExpanded = isExpanded self.shouldShowRecipientsList = shouldShowRecipientsList self.buttonColor = buttonColor + self.index = index } var replyImage: UIImage? { createButtonImage("arrow.turn.up.left") } @@ -138,23 +141,15 @@ public final class ThreadMessageInfoCellNode: CellNode { public private(set) var menuNode = ASButtonNode() public private(set) var expandNode = ASImageNode() - private lazy var recipientsListNode: ASDisplayNode = { - MessageRecipientsNode( - input: .init( - recipients: input.recipients, - ccRecipients: input.ccRecipients, - bccRecipients: input.bccRecipients - ) + private lazy var recipientsListNode: ASDisplayNode = MessageRecipientsNode( + input: .init( + recipients: input.recipients, + ccRecipients: input.ccRecipients, + bccRecipients: input.bccRecipients ) - }() - - private lazy var encryptionNode: BadgeNode = { - BadgeNode(input: input.encryptionBadge) - }() - - private lazy var signatureNode: BadgeNode? = { - input.signatureBadge.map(BadgeNode.init) - }() + ) + private lazy var encryptionNode = BadgeNode(input: input.encryptionBadge) + private lazy var signatureNode: BadgeNode? = input.signatureBadge.map(BadgeNode.init) // MARK: - Properties private let input: ThreadMessageInfoCellNode.Input @@ -170,10 +165,12 @@ public final class ThreadMessageInfoCellNode: CellNode { } // MARK: - Init - public init(input: ThreadMessageInfoCellNode.Input, - onReplyTap: ((ThreadMessageInfoCellNode) -> Void)?, - onMenuTap: ((ThreadMessageInfoCellNode) -> Void)?, - onRecipientsTap: ((ThreadMessageInfoCellNode) -> Void)?) { + public init( + input: ThreadMessageInfoCellNode.Input, + onReplyTap: ((ThreadMessageInfoCellNode) -> Void)?, + onMenuTap: ((ThreadMessageInfoCellNode) -> Void)?, + onRecipientsTap: ((ThreadMessageInfoCellNode) -> Void)? + ) { self.input = input self.onReplyTap = onReplyTap self.onMenuTap = onMenuTap @@ -183,14 +180,13 @@ public final class ThreadMessageInfoCellNode: CellNode { automaticallyManagesSubnodes = true senderNode.attributedText = input.sender - senderNode.accessibilityIdentifier = "aid-message-sender-label" - dateNode.attributedText = input.date setupRecipientButton() setupReplyNode() setupMenuNode() setupExpandNode() + setupAccessibilityIdentifiers() } // MARK: - Setup @@ -205,7 +201,6 @@ public final class ThreadMessageInfoCellNode: CellNode { recipientButtonNode.contentSpacing = 4 recipientButtonNode.addTarget(self, action: #selector(onRecipientsNodeTap), forControlEvents: .touchUpInside) - recipientButtonNode.accessibilityIdentifier = "aid-message-recipients-tappable-area" } private func setupReplyNode() { @@ -244,6 +239,18 @@ public final class ThreadMessageInfoCellNode: CellNode { expandNode.contentMode = .center } + // MARK: - AccessibilityIdentifiers + private func setupAccessibilityIdentifiers() { + recipientButtonNode.accessibilityIdentifier = "aid-message-recipients-tappable-area" + + expandNode.accessibilityIdentifier = "aid-expand-image-\(input.index)" + senderNode.accessibilityIdentifier = "aid-sender-\(input.index)" + dateNode.accessibilityIdentifier = "aid-date-\(input.index)" + + [senderNode, recipientButtonNode, senderNode, dateNode] + .forEach { $0.isAccessibilityElement = true } + } + // MARK: - Callbacks @objc private func onReplyNodeTap() { onReplyTap?(self) diff --git a/FlowCryptUI/Nodes/MessageRecipientsNode.swift b/FlowCryptUI/Nodes/MessageRecipientsNode.swift index b51577d45..228bec9e8 100644 --- a/FlowCryptUI/Nodes/MessageRecipientsNode.swift +++ b/FlowCryptUI/Nodes/MessageRecipientsNode.swift @@ -39,7 +39,6 @@ public final class MessageRecipientsNode: ASDisplayNode { super.init() automaticallyManagesSubnodes = true - setupBorder() } diff --git a/appium/tests/data/index.ts b/appium/tests/data/index.ts index 463cc279e..ebab09bca 100644 --- a/appium/tests/data/index.ts +++ b/appium/tests/data/index.ts @@ -20,6 +20,25 @@ export const CommonData = { message: 'test email from gmail', sender: 'e2e.enterprise.test@flowcrypt.com' }, + threadMessage: { + subject: 'test thread rendering', + sender: 'dmitry@flowcrypt.com', + firstThreadMessage: 'first message', + secondThreadMessage: 'Second thread rendering message\n' + + '\n' + + 'On 04.02.2022 at 11:12 dmitry@flowcrypt.com wrote:\n' + + ' > first message', + thirdThreadMessage: 'Third thread rendering message\n' + + '\n' + + 'On 2022-02-07 at 06:56, e2e.enterprise.test@flowcrypt.com wrote:\n' + + '> Second thread rendering message\n' + + '>\n' + + '> On 04.02.2022 at 11:12 dmitry@flowcrypt.com wrote:\n' + + '> > first message', + firstDate: 'Feb 04', + secondDate: 'Feb 06', + thirdDate: 'Feb 07', + }, sender: { email: 'dmitry@flowcrypt.com', }, diff --git a/appium/tests/screenobjects/email.screen.ts b/appium/tests/screenobjects/email.screen.ts index c96e664c9..a8d088b41 100644 --- a/appium/tests/screenobjects/email.screen.ts +++ b/appium/tests/screenobjects/email.screen.ts @@ -1,6 +1,7 @@ import BaseScreen from './base.screen'; import { CommonData } from "../data"; import ElementHelper from "../helpers/ElementHelper"; +import moment from "moment"; const SELECTORS = { BACK_BTN: '~aid-back-button', @@ -105,10 +106,6 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.CANCEL_BUTTON); } - get senderEmail() { - return $(SELECTORS.SENDER_EMAIL); - } - get encryptionBadge() { return $(SELECTORS.ENCRYPTION_BADGE); } @@ -121,8 +118,14 @@ class EmailScreen extends BaseScreen { return $(SELECTORS.ATTACHMENT_TEXT_VIEW); } - checkEmailAddress = async (email: string) => { - await ElementHelper.checkStaticText(await this.senderEmail, email); + senderEmail = async (index = 0) =>{ + return $(`~aid-sender-${index}`) + } + + checkEmailAddress = async (email: string, index = 0)=> { + const element = await this.senderEmail(index); + await (await element).waitForDisplayed(); + await expect(await (await element).getValue()).toEqual(email); } checkEmailSubject = async (subject: string) => { @@ -130,9 +133,12 @@ class EmailScreen extends BaseScreen { await (await $(selector)).waitForDisplayed(); } - checkEmailText = async (text: string) => { - const selector = `~${text}`; + checkEmailText = async (text: string, index = 0) => { + const selector = `~aid-message-${index}`; await (await $(selector)).waitForDisplayed(); + console.log(await $(selector).getValue()); + + await expect(await $(selector).getValue()).toContain(text) } checkOpenedEmail = async (email: string, subject: string, text: string) => { @@ -141,6 +147,28 @@ class EmailScreen extends BaseScreen { await this.checkEmailText(text); } + checkThreadMessage = async (email: string, subject: string, text: string, date: string, index = 0) => { + await this.checkEmailSubject(subject); + await this.checkEmailAddress(email, index); + await this.clickExpandButtonByIndex(index); + await this.checkEmailText(text, index); + await this.checkDate(date, index); + } + + clickExpandButtonByIndex = async (index: any) => { + const element = (`~aid-expand-image-${index}`); + if(await (await $(element)).isDisplayed()) { + await ElementHelper.waitAndClick(await $(element)); + } + } + + checkDate = async (date: string, index: any) => { + const element = `~aid-date-${index}`; + await (await $(element)).waitForDisplayed(); + const convertedDate = moment(await $(element).getValue()).utcOffset(0).format('MMM DD'); + await expect(convertedDate).toEqual(date) + } + clickBackButton = async () => { await ElementHelper.waitAndClick(await this.backButton); } diff --git a/appium/tests/screenobjects/old-version-app.screen.ts b/appium/tests/screenobjects/old-version-app.screen.ts index ad7ae42ef..b5138deca 100644 --- a/appium/tests/screenobjects/old-version-app.screen.ts +++ b/appium/tests/screenobjects/old-version-app.screen.ts @@ -24,7 +24,7 @@ class OldVersionAppScreen extends BaseScreen { } clickBackButton = async () => { - await ElementHelper.waitAndClick(await this.backButton); + await ElementHelper.waitAndClick(await this.backButton, 500); } checkEmailAddress = async (email: string) => { diff --git a/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts b/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts new file mode 100644 index 000000000..c6416acac --- /dev/null +++ b/appium/tests/specs/live/inbox/CheckThreadRendering.spec.ts @@ -0,0 +1,31 @@ +import { + SplashScreen, + SetupKeyScreen, + MailFolderScreen, + EmailScreen +} from '../../../screenobjects/all-screens'; +import { CommonData } from '../../../data'; + +describe('INBOX: ', () => { + + it('check thread rendering', async () => { + const senderEmail = CommonData.threadMessage.sender; + const emailSubject = CommonData.threadMessage.subject; + const firstMessage = CommonData.threadMessage.firstThreadMessage; + const secondMessage = CommonData.threadMessage.secondThreadMessage; + const thirdMessage = CommonData.threadMessage.thirdThreadMessage; + const userEmail = CommonData.account.email; + const dateFirst = CommonData.threadMessage.firstDate; + const dateSecond = CommonData.threadMessage.secondDate; + const dateThird = CommonData.threadMessage.thirdDate; + + await SplashScreen.login(); + await SetupKeyScreen.setPassPhrase(); + await MailFolderScreen.checkInboxScreen(); + + await MailFolderScreen.clickOnEmailBySubject(emailSubject); + await EmailScreen.checkThreadMessage(senderEmail, emailSubject, thirdMessage, dateThird, 2); + await EmailScreen.checkThreadMessage(userEmail, emailSubject, secondMessage, dateSecond, 1); + await EmailScreen.checkThreadMessage(senderEmail, emailSubject, firstMessage, dateFirst); + }); +});