Add edit banner button UI automated test + accessibility improvements
This commit adds an automated UI test to check if the edit banner button UI is clickable and not hidden behind a top bar or another invisible element. It also improves accessibility support for some elements on login and top bar. Changelog-Changed: Improved accessibility support on some elements Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -338,7 +338,6 @@
|
||||
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CE6DEED27F7A08200C66700 /* Preview Assets.xcassets */; };
|
||||
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEF727F7A08200C66700 /* damusTests.swift */; };
|
||||
4CE6DF0227F7A08200C66700 /* damusUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0127F7A08200C66700 /* damusUITests.swift */; };
|
||||
4CE6DF0427F7A08200C66700 /* damusUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */; };
|
||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */; };
|
||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; };
|
||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; };
|
||||
@@ -1057,6 +1056,11 @@
|
||||
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.swift */; };
|
||||
D7100C5E2B7709ED00C59298 /* PurpleStoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */; };
|
||||
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */; };
|
||||
D71AD8FD2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */; };
|
||||
D71AD8FE2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */; };
|
||||
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */; };
|
||||
D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */; };
|
||||
D71AD9012CEC2398002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */; };
|
||||
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
|
||||
D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
|
||||
D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
|
||||
@@ -2266,7 +2270,6 @@
|
||||
4CE6DEF727F7A08200C66700 /* damusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusTests.swift; sourceTree = "<group>"; };
|
||||
4CE6DEFD27F7A08200C66700 /* damusUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = damusUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4CE6DF0127F7A08200C66700 /* damusUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusUITests.swift; sourceTree = "<group>"; };
|
||||
4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConnection.swift; sourceTree = "<group>"; };
|
||||
4CE8794729941DA700F758CC /* RelayFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilters.swift; sourceTree = "<group>"; };
|
||||
4CE8794D2996B16A00F758CC /* RelayToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayToggle.swift; sourceTree = "<group>"; };
|
||||
@@ -2397,6 +2400,7 @@
|
||||
D7100C5B2B77016700C59298 /* IAPProductStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPProductStateView.swift; sourceTree = "<group>"; };
|
||||
D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleStoreKitManager.swift; sourceTree = "<group>"; };
|
||||
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityTracker.swift; sourceTree = "<group>"; };
|
||||
D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAccessibilityIdentifiers.swift; sourceTree = "<group>"; };
|
||||
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
|
||||
D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = "<group>"; };
|
||||
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
|
||||
@@ -3125,6 +3129,7 @@
|
||||
643EA5C7296B764E005081BB /* RelayFilterView.swift */,
|
||||
D783A63E2AD4E53D00658DDA /* SuggestedHashtagsView.swift */,
|
||||
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */,
|
||||
D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -3634,7 +3639,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CE6DF0127F7A08200C66700 /* damusUITests.swift */,
|
||||
4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */,
|
||||
);
|
||||
path = damusUITests;
|
||||
sourceTree = "<group>";
|
||||
@@ -4669,6 +4673,7 @@
|
||||
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */,
|
||||
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
|
||||
@@ -4805,8 +4810,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D71AD9012CEC2398002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
4CE6DF0227F7A08200C66700 /* damusUITests.swift in Sources */,
|
||||
4CE6DF0427F7A08200C66700 /* damusUITestsLaunchTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -5165,6 +5170,7 @@
|
||||
82D6FC062CD99F7900C925F4 /* ZapTypePicker.swift in Sources */,
|
||||
82D6FC072CD99F7900C925F4 /* ZapUserView.swift in Sources */,
|
||||
82D6FC082CD99F7900C925F4 /* ProfileZapLinkView.swift in Sources */,
|
||||
D71AD8FE2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
82D6FC092CD99F7900C925F4 /* AboutView.swift in Sources */,
|
||||
82D6FC0A2CD99F7900C925F4 /* ProfileName.swift in Sources */,
|
||||
82D6FC0B2CD99F7900C925F4 /* ProfilePictureSelector.swift in Sources */,
|
||||
@@ -5443,6 +5449,7 @@
|
||||
D73E5EB42C6A97F4007EB227 /* NoteContent.swift in Sources */,
|
||||
D73E5EB52C6A97F4007EB227 /* LongformEvent.swift in Sources */,
|
||||
D73E5EB62C6A97F4007EB227 /* PushNotificationClient.swift in Sources */,
|
||||
D71AD8FD2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
D73E5EB72C6A97F4007EB227 /* HighlightEvent.swift in Sources */,
|
||||
D73E5EB82C6A97F4007EB227 /* RelayConnection.swift in Sources */,
|
||||
D73E5EB92C6A97F4007EB227 /* RelayLog.swift in Sources */,
|
||||
@@ -5771,6 +5778,7 @@
|
||||
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */,
|
||||
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
|
||||
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
|
||||
D71AD9002CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
D7CB5D552B11758A00AD4105 /* UnmuteThreadNotify.swift in Sources */,
|
||||
D7CCFC192B058A3F00323D86 /* Block.swift in Sources */,
|
||||
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4CE6DEFC27F7A08200C66700"
|
||||
|
||||
@@ -20,6 +20,7 @@ struct DamusBackground: View {
|
||||
.resizable()
|
||||
.frame(maxWidth: .infinity, maxHeight: maxHeight, alignment: .center)
|
||||
.ignoresSafeArea()
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,14 +239,7 @@ struct ContentView: View {
|
||||
MainContent(damus: damus)
|
||||
.toolbar() {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
isSideBarOpened.toggle()
|
||||
} label: {
|
||||
ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles, disable_animation: damus_state!.settings.disable_animation)
|
||||
.opacity(isSideBarOpened ? 0 : 1)
|
||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||
}
|
||||
.disabled(isSideBarOpened)
|
||||
TopbarSideMenuButton(damus_state: damus, isSideBarOpened: $isSideBarOpened)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
@@ -792,6 +785,25 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct TopbarSideMenuButton: View {
|
||||
let damus_state: DamusState
|
||||
@Binding var isSideBarOpened: Bool
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
isSideBarOpened.toggle()
|
||||
} label: {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
.opacity(isSideBarOpened ? 0 : 1)
|
||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||
.accessibilityHidden(true) // Knowing there is a profile picture here leads to no actionable outcome to VoiceOver users, so it is best not to show it
|
||||
}
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.main_side_menu_button.rawValue)
|
||||
.accessibilityLabel(NSLocalizedString("Side menu", comment: "Accessibility label for the side menu button at the topbar"))
|
||||
.disabled(isSideBarOpened)
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView(keypair: Keypair(pubkey: test_pubkey, privkey: nil), appDelegate: nil)
|
||||
|
||||
66
damus/Views/AppAccessibilityIdentifiers.swift
Normal file
66
damus/Views/AppAccessibilityIdentifiers.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// AppAccessibilityIdentifiers.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2024-11-18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A collection of app-wide identifier constants used to facilitate UI tests to find the element they are looking for.
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - This is not an exhaustive list. Add more identifiers as needed.
|
||||
/// - Organize this by separating each category with `MARK` comment markers and a unique prefix, each category separated by 2 empty lines
|
||||
enum AppAccessibilityIdentifiers: String {
|
||||
// MARK: Login
|
||||
// Prefix: `sign_in`
|
||||
|
||||
/// Sign in button at the very start of the app
|
||||
case sign_in_option_button
|
||||
/// A secure text entry field where the user can put their private key when logging in
|
||||
case sign_in_nsec_key_entry_field
|
||||
/// Button to sign in after entering private key
|
||||
case sign_in_confirm_button
|
||||
|
||||
|
||||
// MARK: Onboarding
|
||||
// Prefix: `onboarding`
|
||||
|
||||
/// The skip button on the onboarding sheet
|
||||
case onboarding_sheet_skip_button
|
||||
|
||||
|
||||
// MARK: Post composer
|
||||
// Prefix: `post_composer`
|
||||
|
||||
/// The cancel post button
|
||||
case post_composer_cancel_button
|
||||
|
||||
// MARK: Main interface layout
|
||||
// Prefix: `main`
|
||||
|
||||
/// Profile picture item on the top toolbar, used to open the side menu
|
||||
case main_side_menu_button
|
||||
|
||||
|
||||
// MARK: Side menu
|
||||
// Prefix: `side_menu`
|
||||
|
||||
/// The profile option in the side menu
|
||||
case side_menu_profile_button
|
||||
|
||||
|
||||
// MARK: Items specific to the user's own profile
|
||||
// Prefix: `own_profile`
|
||||
|
||||
/// The edit profile button
|
||||
case own_profile_edit_button
|
||||
|
||||
/// The button to edit the banner image on the profile
|
||||
case own_profile_banner_image_edit_button
|
||||
|
||||
/// The button to pick the new banner image from URL
|
||||
case own_profile_banner_image_edit_from_url
|
||||
}
|
||||
@@ -32,15 +32,21 @@ struct EditBannerImageView: View {
|
||||
.onFailureImage(defaultImage)
|
||||
.kfClickable()
|
||||
|
||||
if #available(iOS 17.0, *) {
|
||||
EditPictureControl(uploader: damus_state.settings.default_media_uploader, keypair: damus_state.keypair, pubkey: damus_state.pubkey, image_url: $banner_image, uploadObserver: viewModel, callback: callback)
|
||||
.padding(10)
|
||||
.safeAreaPadding(self.safeAreaInsets)
|
||||
} else {
|
||||
EditPictureControl(uploader: damus_state.settings.default_media_uploader, keypair: damus_state.keypair, pubkey: damus_state.pubkey, image_url: $banner_image, uploadObserver: viewModel, callback: callback)
|
||||
.padding(10)
|
||||
.padding(.top, self.safeAreaInsets.top)
|
||||
}
|
||||
EditPictureControl(uploader: damus_state.settings.default_media_uploader, keypair: damus_state.keypair, pubkey: damus_state.pubkey, image_url: $banner_image, uploadObserver: viewModel, callback: callback)
|
||||
.padding(10)
|
||||
.backwardsCompatibleSafeAreaPadding(self.safeAreaInsets)
|
||||
.accessibilityLabel(NSLocalizedString("Edit banner image", comment: "Accessibility label for edit banner image button"))
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.own_profile_banner_image_edit_button.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
fileprivate func backwardsCompatibleSafeAreaPadding(_ insets: EdgeInsets) -> some View {
|
||||
if #available(iOS 17.0, *) {
|
||||
return self.safeAreaPadding(insets)
|
||||
} else {
|
||||
return self.padding(.top, insets.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ struct LoginView: View {
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
}
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.sign_in_confirm_button.rawValue)
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
.padding(.top, 10)
|
||||
}
|
||||
@@ -299,27 +300,35 @@ struct KeyInput: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: "doc.on.clipboard")
|
||||
.foregroundColor(.gray)
|
||||
.onTapGesture {
|
||||
if let pastedkey = UIPasteboard.general.string {
|
||||
self.key.wrappedValue = pastedkey
|
||||
}
|
||||
Button(action: {
|
||||
if let pastedkey = UIPasteboard.general.string {
|
||||
self.key.wrappedValue = pastedkey
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: "doc.on.clipboard")
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
.accessibilityLabel(NSLocalizedString("Paste private key", comment: "Accessibility label for the private key paste button"))
|
||||
|
||||
SignInScan(shouldSaveKey: shouldSaveKey, loginKey: key, privKeyFound: privKeyFound)
|
||||
|
||||
if is_secured {
|
||||
SecureField("", text: key)
|
||||
.nsecLoginStyle(key: key.wrappedValue, title: title)
|
||||
} else {
|
||||
TextField("", text: key)
|
||||
.nsecLoginStyle(key: key.wrappedValue, title: title)
|
||||
}
|
||||
Image(systemName: "eye.slash")
|
||||
.foregroundColor(.gray)
|
||||
.onTapGesture {
|
||||
is_secured.toggle()
|
||||
}
|
||||
SecureField("", text: key)
|
||||
.nsecLoginStyle(key: key.wrappedValue, title: title)
|
||||
.accessibilityLabel(NSLocalizedString("Account private key", comment: "Accessibility label for the private key input field"))
|
||||
} else {
|
||||
TextField("", text: key)
|
||||
.nsecLoginStyle(key: key.wrappedValue, title: title)
|
||||
.accessibilityLabel(NSLocalizedString("Account private key", comment: "Accessibility label for the private key input field"))
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
is_secured.toggle()
|
||||
}, label: {
|
||||
Image(systemName: "eye.slash")
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
.accessibilityLabel(NSLocalizedString("Toggle key visibility", comment: "Accessibility label for toggling the visibility of the private key input field"))
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 10)
|
||||
@@ -342,6 +351,7 @@ struct SignInHeader: View {
|
||||
.frame(width: 56, height: 56, alignment: .center)
|
||||
.shadow(color: DamusColors.purple, radius: 2)
|
||||
.padding(.bottom)
|
||||
.accessibilityLabel(NSLocalizedString("Damus logo", comment: "Accessibility label for damus logo"))
|
||||
|
||||
Text("Sign in", comment: "Title of view to log into an account.")
|
||||
.foregroundColor(DamusColors.neutral6)
|
||||
@@ -365,10 +375,12 @@ struct SignInEntry: View {
|
||||
.fontWeight(.medium)
|
||||
.padding(.top, 30)
|
||||
|
||||
KeyInput(NSLocalizedString("nsec1...", comment: "Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key."),
|
||||
KeyInput(NSLocalizedString("nsec1…", comment: "Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key."),
|
||||
key: key,
|
||||
shouldSaveKey: shouldSaveKey,
|
||||
privKeyFound: $privKeyFound)
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.sign_in_nsec_key_entry_field.rawValue)
|
||||
|
||||
if privKeyFound {
|
||||
Toggle(NSLocalizedString("Save Key in Secure Keychain", comment: "Toggle to save private key to the Apple secure keychain."), isOn: shouldSaveKey)
|
||||
}
|
||||
@@ -389,7 +401,7 @@ struct SignInScan: View {
|
||||
Button(action: { showQR.toggle() }, label: {
|
||||
Image(systemName: "qrcode.viewfinder")})
|
||||
.foregroundColor(.gray)
|
||||
|
||||
.accessibilityLabel(NSLocalizedString("Scan QR code", comment: "Accessibility label for a button that scans a private key QR code"))
|
||||
}
|
||||
.sheet(isPresented: $showQR, onDismiss: {
|
||||
if qrkey == nil { resetView() }}
|
||||
|
||||
@@ -38,7 +38,9 @@ struct OnboardingSuggestionsView: View {
|
||||
}, label: {
|
||||
Text("Skip", comment: "Button to dismiss the suggested users screen")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
}))
|
||||
})
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.onboarding_sheet_skip_button.rawValue)
|
||||
)
|
||||
.tag(0)
|
||||
|
||||
PostView(
|
||||
|
||||
@@ -304,6 +304,7 @@ struct PostView: View {
|
||||
.padding(10)
|
||||
})
|
||||
.buttonStyle(NeutralButtonStyle())
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.post_composer_cancel_button.rawValue)
|
||||
|
||||
if let error {
|
||||
Text(error)
|
||||
|
||||
@@ -41,6 +41,7 @@ struct EditPictureControl: View {
|
||||
}) {
|
||||
Text("Image URL", comment: "Option to enter a url")
|
||||
}
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.own_profile_banner_image_edit_from_url.rawValue)
|
||||
|
||||
Button(action: {
|
||||
self.show_library = true
|
||||
|
||||
@@ -27,6 +27,7 @@ struct ProfileEditButton: View {
|
||||
.minimumScaleFactor(0.5)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.own_profile_edit_button.rawValue)
|
||||
}
|
||||
|
||||
func fillColor() -> Color {
|
||||
|
||||
@@ -56,6 +56,7 @@ struct SetupView: View {
|
||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
}
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.sign_in_option_button.rawValue)
|
||||
.padding()
|
||||
|
||||
Button(action: {
|
||||
|
||||
@@ -40,6 +40,7 @@ struct SideMenuView: View {
|
||||
NavigationLink(value: Route.Profile(profile: profile_model, followers: followers)) {
|
||||
navLabel(title: NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), img: "user")
|
||||
}
|
||||
.accessibilityIdentifier(AppAccessibilityIdentifiers.side_menu_profile_button.rawValue)
|
||||
|
||||
NavigationLink(value: Route.Wallet(wallet: damus_state.wallet)) {
|
||||
navLabel(title: NSLocalizedString("Wallet", comment: "Sidebar menu label for Wallet view."), img: "wallet")
|
||||
|
||||
@@ -49,14 +49,7 @@ struct PostingTimelineView: View {
|
||||
.frame(height: getSafeAreaTop())
|
||||
|
||||
HStack(alignment: .top) {
|
||||
Button {
|
||||
isSideBarOpened.toggle()
|
||||
} label: {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
.opacity(isSideBarOpened ? 0 : 1)
|
||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||
}
|
||||
.disabled(isSideBarOpened)
|
||||
TopbarSideMenuButton(damus_state: damus_state, isSideBarOpened: $isSideBarOpened)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
@@ -8,34 +8,85 @@
|
||||
import XCTest
|
||||
|
||||
class damusUITests: XCTestCase {
|
||||
var app = XCUIApplication()
|
||||
typealias AID = AppAccessibilityIdentifiers
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
self.app = XCUIApplication()
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||
|
||||
// Set app language to English
|
||||
app.launchArguments += ["-AppleLanguages", "(en)"]
|
||||
app.launchArguments += ["-AppleLocale", "en_US"]
|
||||
|
||||
// Force portrait orientation
|
||||
XCUIDevice.shared.orientation = .portrait
|
||||
|
||||
// Optional: Reset the device's orientation before each test
|
||||
addTeardownBlock {
|
||||
XCUIDevice.shared.orientation = .portrait
|
||||
}
|
||||
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
/// Tests if banner edit button is clickable.
|
||||
/// Note: This is able to detect if the button is obscured by an invisible overlaying object.
|
||||
/// See https://github.com/damus-io/damus/issues/2636 for the kind of issue this guards against.
|
||||
func testEditBannerImage() throws {
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
try self.loginIfNotAlready()
|
||||
|
||||
guard app.buttons[AID.main_side_menu_button.rawValue].tapIfExists(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
guard app.buttons[AID.side_menu_profile_button.rawValue].tapIfExists(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
guard app.buttons[AID.own_profile_edit_button.rawValue].tapIfExists(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
|
||||
guard app.buttons[AID.own_profile_banner_image_edit_button.rawValue].waitForExistence(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
let bannerEditButtonCoordinates = app.buttons[AID.own_profile_banner_image_edit_button.rawValue].coordinate(withNormalizedOffset: CGVector.zero).withOffset(CGVector(dx: 15, dy: 15))
|
||||
bannerEditButtonCoordinates.tap()
|
||||
|
||||
guard app.buttons[AID.own_profile_banner_image_edit_from_url.rawValue].waitForExistence(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
}
|
||||
|
||||
func testLaunchPerformance() throws {
|
||||
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||
// This measures how long it takes to launch your application.
|
||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||
XCUIApplication().launch()
|
||||
}
|
||||
|
||||
func loginIfNotAlready() throws {
|
||||
if app.buttons[AID.sign_in_option_button.rawValue].waitForExistence(timeout: 5) {
|
||||
try self.login()
|
||||
}
|
||||
|
||||
app.buttons[AID.onboarding_sheet_skip_button.rawValue].tapIfExists(timeout: 5)
|
||||
app.buttons[AID.post_composer_cancel_button.rawValue].tapIfExists(timeout: 5)
|
||||
}
|
||||
|
||||
func login() throws {
|
||||
app.buttons[AID.sign_in_option_button.rawValue].tap()
|
||||
|
||||
guard app.secureTextFields[AID.sign_in_nsec_key_entry_field.rawValue].tapIfExists(timeout: 10) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
app.typeText("nsec1vxvz8c7070d99njn0aqpcttljnzhfutt422l0r37yep7htesd0mq9p8fg2")
|
||||
|
||||
guard app.buttons[AID.sign_in_confirm_button.rawValue].tapIfExists(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element }
|
||||
}
|
||||
|
||||
enum DamusUITestError: Error {
|
||||
case timeout_waiting_for_element
|
||||
}
|
||||
}
|
||||
|
||||
extension XCUIElement {
|
||||
@discardableResult
|
||||
func tapIfExists(timeout: TimeInterval) -> Bool {
|
||||
if self.waitForExistence(timeout: timeout) {
|
||||
self.tap()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// damusUITestsLaunchTests.swift
|
||||
// damusUITests
|
||||
//
|
||||
// Created by William Casarin on 2022-04-01.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class damusUITestsLaunchTests: XCTestCase {
|
||||
|
||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testLaunch() throws {
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||
// such as logging into a test account or navigating somewhere in the app
|
||||
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = "Launch Screen"
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user