Align accessibility IDs with elementX android (#567)

* Rename onboarding and login user/pass accessibility ids.

* Refactor remaining server change accessibility IDs

* Avoid accessibilityIdentifier spreading to all decorations on the textBox.

Required because otherwise there are multiple items tagged with the accessibilityId
which means we can't click on "the" item.

* Move all accessibility identifiers to `AccessibilityIdentifiers.swft`
- use same naming convention on all of them
- remove the unused ones
- fix build errors in integration tests and invalid identifiers (still broken until autodiscovery lands on rosa)

---------

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
Michael Kaye
2023-02-15 05:53:04 +00:00
committed by GitHub
parent 21154c7eef
commit a06365517c
29 changed files with 215 additions and 141 deletions
@@ -0,0 +1,89 @@
//
// Copyright 2023 New Vector Ltd
//
// 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 Foundation
struct A11yIdentifiers {
static let bugReportScreen = BugReportScreen()
static let changeServerScreen = ChangeServer()
static let homeScreen = HomeScreen()
static let loginScreen = LoginScreen()
static let onboardingScreen = OnboardingScreen()
static let roomScreen = RoomScreen()
static let sessionVerificationScreen = SessionVerificationScreen()
static let softLogoutScreen = SoftLogoutScreen()
struct BugReportScreen {
let report = "bug_report-report"
let sendLogs = "bug_report-send_logs"
let screenshot = "bug_report-screenshot"
let removeScreenshot = "bug_report-remove_screenshot"
let send = "bug_report-send"
}
struct ChangeServer {
let server = "change_server-server"
let `continue` = "change_server-continue"
let dismiss = "change_server-dismiss"
}
struct HomeScreen {
let userAvatar = "home_screen-user_avatar"
func roomName(_ name: String) -> String {
"home_screen-room_name:\(name)"
}
}
struct LoginScreen {
let emailUsername = "login-email_username"
let password = "login-password"
let `continue` = "login-continue"
let changeServer = "login-change_server"
let oidc = "login-oidc"
let unsupportedServer = "login-unsupported_server"
}
struct OnboardingScreen {
let signIn = "onboarding-sign_in"
let hidden = "onboarding-hidden"
}
struct RoomScreen {
let name = "room_name"
let avatar = "room_avatar"
}
struct SessionVerificationScreen {
let requestVerification = "session_verification-request_verification"
let startSasVerification = "session_verification-start_sas_verification"
let acceptChallenge = "session_verification-accept_challenge"
let declineChallenge = "session_verification-accept_challenge"
let close = "session_verification-close"
}
struct SoftLogoutScreen {
let title = "soft_logout-title"
let message = "soft_logout-message"
let password = "soft_logout-password"
let forgotPassword = "soft_logout-forgot_password"
let next = "soft_logout-next"
let unsupportedServer = "soft_logout-unsupported_server"
let clearDataTitle = "soft_logout-clear_data_title"
let clearDataMessage = "soft_logout-clear_data_message"
let clearData = "soft_logout-clear_data"
}
}
@@ -99,14 +99,12 @@ struct AnalyticsPrompt: View {
.font(.element.bodyBold)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("enableButton")
Button { context.send(viewAction: .disable) } label: {
Text(ElementL10n.actionNotNow)
.font(.element.bodyBold)
.padding(12)
}
.accessibilityIdentifier("disableButton")
}
}
}
@@ -77,23 +77,21 @@ struct LoginScreen: View {
TextField(ElementL10n.loginSigninUsernameHint, text: $context.username)
.focused($isUsernameFocused)
.textFieldStyle(.elementInput())
.textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.emailUsername))
.disableAutocorrection(true)
.textContentType(.username)
.autocapitalization(.none)
.submitLabel(.next)
.onChange(of: isUsernameFocused, perform: usernameFocusChanged)
.onSubmit { isPasswordFocused = true }
.accessibilityIdentifier("usernameTextField")
.padding(.bottom, 20)
SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password)
.focused($isPasswordFocused)
.textFieldStyle(.elementInput())
.textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.password))
.textContentType(.password)
.submitLabel(.done)
.onSubmit(submit)
.accessibilityIdentifier("passwordTextField")
Spacer().frame(height: 32)
@@ -102,7 +100,7 @@ struct LoginScreen: View {
}
.buttonStyle(.elementAction(.xLarge))
.disabled(!context.viewState.canSubmit)
.accessibilityIdentifier("nextButton")
.accessibilityIdentifier(A11yIdentifiers.loginScreen.continue)
}
}
@@ -112,7 +110,7 @@ struct LoginScreen: View {
Text(ElementL10n.loginContinue)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("oidcButton")
.accessibilityIdentifier(A11yIdentifiers.loginScreen.oidc)
}
/// Text shown if neither password or OIDC login is supported.
@@ -122,7 +120,7 @@ struct LoginScreen: View {
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
.frame(maxWidth: .infinity)
.accessibilityIdentifier("unsupportedServerText")
.accessibilityIdentifier(A11yIdentifiers.loginScreen.unsupportedServer)
}
/// Parses the username for a homeserver.
@@ -51,7 +51,7 @@ struct LoginServerInfoSection: View {
}
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system))
}
.accessibilityIdentifier("editServerButton")
.accessibilityIdentifier(A11yIdentifiers.loginScreen.changeServer)
}
}
}
@@ -62,21 +62,21 @@ struct ServerSelectionScreen: View {
TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress)
.textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl,
footerText: context.viewState.footerMessage,
isError: context.viewState.isShowingFooterError))
isError: context.viewState.isShowingFooterError,
accessibilityIdentifier: A11yIdentifiers.changeServerScreen.server))
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: context.homeserverAddress) { _ in context.send(viewAction: .clearFooterError) }
.submitLabel(.done)
.onSubmit(submit)
.accessibilityIdentifier("addressTextField")
Button(action: submit) {
Text(context.viewState.buttonTitle)
}
.buttonStyle(.elementAction(.xLarge))
.disabled(context.viewState.hasValidationError)
.accessibilityIdentifier("confirmButton")
.accessibilityIdentifier(A11yIdentifiers.changeServerScreen.continue)
}
}
@@ -87,7 +87,7 @@ struct ServerSelectionScreen: View {
Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel)
}
.accessibilityIdentifier("dismissButton")
.accessibilityIdentifier(A11yIdentifiers.changeServerScreen.dismiss)
}
}
}
@@ -58,20 +58,19 @@ struct SoftLogoutScreen: View {
.font(.element.title2Bold)
.multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("titleLabel")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.title)
Text(ElementL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId))
.font(.element.body)
.multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("messageLabel1")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.message)
if context.viewState.showRecoverEncryptionKeysMessage {
Text(ElementL10n.softLogoutSigninE2eWarningNotice)
.font(.element.body)
.multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("messageLabel2")
}
}
}
@@ -85,7 +84,7 @@ struct SoftLogoutScreen: View {
.textContentType(.password)
.submitLabel(.done)
.onSubmit(submit)
.accessibilityIdentifier("passwordTextField")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.password)
Button { context.send(viewAction: .forgotPassword) } label: {
Text(ElementL10n.ftueAuthForgotPassword)
@@ -93,14 +92,14 @@ struct SoftLogoutScreen: View {
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.bottom, 8)
.accessibilityIdentifier("forgotPasswordButton")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.forgotPassword)
Button(action: submit) {
Text(ElementL10n.loginSignupSubmit)
}
.buttonStyle(.elementAction(.xLarge))
.disabled(!context.viewState.canSubmit)
.accessibilityIdentifier("nextButton")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.next)
}
}
@@ -110,7 +109,6 @@ struct SoftLogoutScreen: View {
Text(ElementL10n.loginContinue)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("oidcButton")
}
/// Text shown if neither password or OIDC login is supported.
@@ -120,7 +118,6 @@ struct SoftLogoutScreen: View {
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
.frame(maxWidth: .infinity)
.accessibilityIdentifier("unsupportedServerText")
}
/// The text field and submit button where the user enters an email address.
@@ -130,20 +127,20 @@ struct SoftLogoutScreen: View {
.font(.element.title2Bold)
.multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("clearDataTitleLabel")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataTitle)
Text(ElementL10n.softLogoutClearDataNotice)
.font(.element.body)
.multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("clearDataMessageLabel")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataMessage)
.padding(.bottom, 12)
Button(action: clearData) {
Text(ElementL10n.softLogoutClearDataSubmit)
}
.buttonStyle(.elementAction(.xLarge, color: .element.alert))
.accessibilityIdentifier("clearDataButton")
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearData)
.alert(ElementL10n.softLogoutClearDataDialogTitle,
isPresented: $showingClearDataConfirmation) {
Button(ElementL10n.actionSignOut,
@@ -59,7 +59,7 @@ struct BugReportScreen: View {
.padding(.horizontal, 10)
.padding(.vertical, 4)
.cornerRadius(14)
.accessibilityIdentifier("reportTextView")
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.report)
.scrollContentBackground(.hidden)
if context.reportText.isEmpty {
@@ -84,7 +84,7 @@ struct BugReportScreen: View {
VStack(spacing: 8) {
Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled)
.tint(Color.element.brand)
.accessibilityIdentifier("sendLogsToggle")
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
.padding(.horizontal, 16)
.padding(.vertical, 11)
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground))
@@ -104,12 +104,12 @@ struct BugReportScreen: View {
.resizable()
.scaledToFit()
.frame(width: 100)
.accessibilityIdentifier("screenshotImage")
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.screenshot)
Button { context.send(viewAction: .removeScreenshot) } label: {
Image(uiImage: Asset.Images.closeCircle.image)
}
.offset(x: 10, y: -10)
.accessibilityIdentifier("removeScreenshotButton")
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.removeScreenshot)
}
.padding(.vertical, 16)
.padding(.horizontal, 16)
@@ -131,7 +131,7 @@ struct BugReportScreen: View {
context.send(viewAction: .submit)
}
.disabled(context.reportText.count < 5)
.accessibilityIdentifier("sendButton")
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.send)
}
}
}
@@ -56,7 +56,6 @@ struct EmojiPickerScreen: View {
Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel)
}
.accessibilityIdentifier("dismissButton")
}
}
}
@@ -117,7 +117,7 @@ struct HomeScreen: View {
contentID: context.viewState.userID,
avatarSize: .user(on: .home),
imageProvider: context.imageProvider)
.accessibilityIdentifier("userAvatarImage")
.accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar)
}
.alert(ElementL10n.actionSignOut,
isPresented: $showingLogoutConfirmation) {
@@ -38,7 +38,7 @@ struct HomeScreenRoomCell: View {
.accessibilityElement(children: .combine)
}
.buttonStyle(HomeScreenRoomCellButtonStyle())
.accessibilityIdentifier("roomName:\(room.name)")
.accessibilityIdentifier(A11yIdentifiers.homeScreen.roomName(room.name))
.overlay(alignment: .bottom) {
Divider()
.frame(height: 0.5)
@@ -47,7 +47,7 @@ struct OnboardingScreen: View {
// Add a hidden page at the start of the carousel duplicating the content of the last page
OnboardingPageView(content: context.viewState.content[pageCount - 1])
.frame(width: geometry.size.width)
.accessibilityIdentifier("hiddenPage")
.accessibilityIdentifier(A11yIdentifiers.onboardingScreen.hidden)
ForEach(0..<pageCount, id: \.self) { index in
OnboardingPageView(content: context.viewState.content[index])
@@ -93,7 +93,7 @@ struct OnboardingScreen: View {
Text(ElementL10n.loginContinue)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("getStartedButton")
.accessibilityIdentifier(A11yIdentifiers.onboardingScreen.signIn)
}
.padding(.horizontal, verticalSizeClass == .compact ? 128 : 24)
.readableFrame()
@@ -53,7 +53,6 @@ struct RoomDetailsScreen: View {
contentID: context.viewState.roomId,
avatarSize: .room(on: .details),
imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage")
Text(context.viewState.title)
.foregroundColor(.element.primaryContent)
@@ -129,7 +128,6 @@ struct RoomDetailsScreen: View {
}
.listRowInsets(listRowInsets)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("peopleButton")
.disabled(context.viewState.isLoadingMembers)
} header: {
Text(ElementL10n.roomDetailsAboutSectionTitle)
@@ -29,7 +29,7 @@ struct RoomHeaderView: View {
.accessibilityHidden(true)
Text(context.viewState.roomTitle)
.font(.element.headline)
.accessibilityIdentifier("roomNameLabel")
.accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
}
// Leading align whilst using the principal toolbar position.
.frame(maxWidth: .infinity, alignment: .leading)
@@ -45,7 +45,7 @@ struct RoomHeaderView: View {
contentID: context.viewState.roomId,
avatarSize: .room(on: .timeline),
imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage")
.accessibilityIdentifier(A11yIdentifiers.roomScreen.avatar)
}
}
@@ -79,14 +79,12 @@ struct SessionVerificationScreen: View {
.font(.title2.bold())
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("titleLabel")
.padding(.bottom, 8)
Text(context.viewState.message)
.font(.subheadline)
.multilineTextAlignment(.center)
.foregroundColor(.element.tertiaryContent)
.accessibilityIdentifier("detailLabel")
}
}
@@ -129,21 +127,20 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .requestVerification)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("requestVerificationButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
case .cancelled:
Button(ElementL10n.globalRetry) {
context.send(viewAction: .restart)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("restartButton")
case .verificationRequestAccepted:
Button(ElementL10n.sessionVerificationStart) {
context.send(viewAction: .startSasVerification)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("sasVerificationStartButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.startSasVerification)
case .showingChallenge:
VStack(spacing: 30) {
@@ -151,13 +148,13 @@ struct SessionVerificationScreen: View {
Label(ElementL10n.verificationSasMatch, systemImage: "checkmark")
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline)
}
.font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
}
case .acceptingChallenge:
@@ -170,14 +167,14 @@ struct SessionVerificationScreen: View {
}
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
.disabled(true)
Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline)
}
.font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
.disabled(true)
}
@@ -193,7 +190,7 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .close)
}
.foregroundColor(.element.accent)
.accessibilityIdentifier("closeButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.close)
}
}
@@ -86,7 +86,6 @@ struct SettingsScreen: View {
image: Image(systemName: "checkmark.shield")) {
context.send(viewAction: .sessionVerification)
}
.accessibilityIdentifier("sessionVerificationButton")
}
}
@@ -147,7 +146,6 @@ struct SettingsScreen: View {
private var doneButton: some View {
Button(ElementL10n.done, action: close)
.accessibilityIdentifier("closeButton")
}
private func close() {