Fix A11y tests (#5104)

* replace NavigationStack with ElementNavigationStack to allow the content to be rendered without a NavigationStack in a11y tests

* fix a11y tests

* update xcodeproject

* swiftformat fix

* use iOS 26.1 for CI

* use a wrapper to solve the issue for a11y tests

* ElementNavigationStack only uses the trick in DEBUG mode, and added a swiftlint rule to prevent the usage of NavigationStack
This commit is contained in:
Mauro
2026-02-13 16:45:58 +01:00
committed by GitHub
parent 32b0a0985c
commit 56eec826df
83 changed files with 254 additions and 180 deletions

View File

@@ -9,7 +9,7 @@ on:
jobs:
tests:
name: Tests
runs-on: macos-15
runs-on: macos-26
timeout-minutes: 150
concurrency:

View File

@@ -4,7 +4,7 @@ disabled_rules:
- redundant_discardable_let
- identifier_name
opt_in_rules:
opt_in_rules:
- force_unwrapping
- private_action
- explicit_init
@@ -88,3 +88,11 @@ custom_rules:
match_kinds: identifier
message: "MXLog should be used instead of os_log()"
severity: error
prefer_element_navigation_stack:
regex: "\\b(NavigationStack)\\b"
match_kinds: identifier
message: "Use ElementNavigationStack instead of NavigationStack"
severity: error
included: "ElementX/Sources/.*\\.swift"
excluded: "ElementNavigationStack.swift"

View File

@@ -1211,6 +1211,7 @@
D2466C6BC8CAD8FADD7BF89B /* RoomPreviewProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6695C64F066628411EAD21E9 /* RoomPreviewProxyMock.swift */; };
D26093BB80B69092B0E9AC7C /* PinnedItemsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */; };
D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */; };
D29E999538E5ABC00E1668F8 /* ElementNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */; };
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
D304343515CE0464C5089537 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD5523BDEDB247E29228476 /* AppSettings.swift */; };
D31B34B3902BC597593F3ABB /* preview_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 200626E8353AB2729444F991 /* preview_image.jpg */; };
@@ -2864,6 +2865,7 @@
F0096BC5DA86AF6B6E5742AC /* RoomPermissionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPermissionsTests.swift; sourceTree = "<group>"; };
F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
F0205C03F98BE861EDABCB0D /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Localizable.strings; sourceTree = "<group>"; };
F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationStack.swift; sourceTree = "<group>"; };
F08776C48FFB47CACF64ED10 /* ServerConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
F0E14FF533D25A0692F7CEB0 /* RoomPollsHistoryScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModel.swift; sourceTree = "<group>"; };
F1033290D99D5BBA1AF3560A /* MapTilerURLBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerURLBuilderProtocol.swift; sourceTree = "<group>"; };
@@ -3121,6 +3123,7 @@
isa = PBXGroup;
children = (
76A46ABD27628CB5FC402541 /* Backports.swift */,
F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */,
693E16574C6F7F9FA1015A8C /* Search.swift */,
832397B5C3D00A4BF52C5F0B /* ShouldScrollOnKeyboardDidShow.swift */,
D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */,
@@ -8058,6 +8061,7 @@
48416BBEB8DDF3E4DED0EDB6 /* ElementCallServiceProtocol.swift in Sources */,
07CC13C5729C24255348CBBD /* ElementCallWidgetDriver.swift in Sources */,
370AF5BFCD4384DD455479B6 /* ElementCallWidgetDriverProtocol.swift in Sources */,
D29E999538E5ABC00E1668F8 /* ElementNavigationStack.swift in Sources */,
A87DC550659C5176AC1829DE /* ElementTextFieldStyle.swift in Sources */,
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */,
3A08584ECDD4A4541DBF21F8 /* EmojiLoaderProtocol.swift in Sources */,

View File

@@ -1,5 +1,6 @@
"Notification" = "Notification";
"a11y_add_reaction" = "Add reaction: %1$@";
"a11y_address" = "Address";
"a11y_avatar" = "Avatar";
"a11y_collapse_message_text_field" = "Minimize message text field";
"a11y_delete" = "Delete";
@@ -22,6 +23,7 @@
"a11y_poll_end" = "Ended poll";
"a11y_polls_will_remove_selection" = "Will remove previous selection";
"a11y_polls_winning_answer" = "This is the winning answer";
"a11y_qr_code" = "QR Code";
"a11y_react_with" = "React with %1$@";
"a11y_react_with_other_emojis" = "React with other emojis";
"a11y_read_receipts_multiple" = "Read by %1$@ and %2$@";

View File

@@ -1,5 +1,6 @@
"Notification" = "Notification";
"a11y_add_reaction" = "Add reaction: %1$@";
"a11y_address" = "Address";
"a11y_avatar" = "Avatar";
"a11y_collapse_message_text_field" = "Minimise message text field";
"a11y_delete" = "Delete";
@@ -22,6 +23,7 @@
"a11y_poll_end" = "Ended poll";
"a11y_polls_will_remove_selection" = "Will remove previous selection";
"a11y_polls_winning_answer" = "This is the winning answer";
"a11y_qr_code" = "QR Code";
"a11y_react_with" = "React with %1$@";
"a11y_react_with_other_emojis" = "React with other emojis";
"a11y_read_receipts_multiple" = "Read by %1$@ and %2$@";

View File

@@ -194,6 +194,22 @@
<string>%d notifications</string>
</dict>
</dict>
<key>notification_fallback_n_content</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@COUNT@</string>
<key>COUNT</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>You have %d new message.</string>
<key>other</key>
<string>You have %d new messages.</string>
</dict>
</dict>
<key>notification_invitations</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

View File

@@ -104,11 +104,8 @@ struct PreviewsWrapperView: View {
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
if wrapper.currentIndex < 0 || wrapper.isDone {
EmptyView()
} else {
if wrapper.currentIndex >= 0, !wrapper.isDone {
wrapper.currentPreview.content
// This ID raises UIKit assertions on iOS 26 but is needed otherwise toolbars go missing and some timeline items won't resize.
.id("\(wrapper.previewName)-\(dynamicTypeSize)")
}
}

View File

@@ -365,6 +365,7 @@ private struct NavigationSplitCoordinatorView: View {
/// The NavigationStack that will be used in compact layouts
var navigationStack: some View {
// swiftlint:disable:next prefer_element_navigation_stack
NavigationStack(path: $navigationSplitCoordinator.compactLayoutStackModules) {
navigationSplitCoordinator.compactLayoutRootModule?.coordinator?.toPresentable()
.id(navigationSplitCoordinator.compactLayoutRootModule?.id) // Is a nil ID ok?
@@ -691,6 +692,7 @@ private struct NavigationStackCoordinatorView: View {
@Bindable var navigationStackCoordinator: NavigationStackCoordinator
var body: some View {
// swiftlint:disable:next prefer_element_navigation_stack
NavigationStack(path: $navigationStackCoordinator.stackModules) {
navigationStackCoordinator.rootModule?.coordinator?.toPresentable()
.id(navigationStackCoordinator.rootModule?.id) // Is a nil ID ok?

View File

@@ -14,6 +14,8 @@ internal enum L10n {
internal static func a11yAddReaction(_ p1: Any) -> String {
return L10n.tr("Localizable", "a11y_add_reaction", String(describing: p1))
}
/// Address
internal static var a11yAddress: String { return L10n.tr("Localizable", "a11y_address") }
/// Avatar
internal static var a11yAvatar: String { return L10n.tr("Localizable", "a11y_avatar") }
/// Minimise message text field
@@ -74,6 +76,8 @@ internal enum L10n {
internal static var a11yPollsWillRemoveSelection: String { return L10n.tr("Localizable", "a11y_polls_will_remove_selection") }
/// This is the winning answer
internal static var a11yPollsWinningAnswer: String { return L10n.tr("Localizable", "a11y_polls_winning_answer") }
/// QR Code
internal static var a11yQrCode: String { return L10n.tr("Localizable", "a11y_qr_code") }
/// React with %1$@
internal static func a11yReactWith(_ p1: Any) -> String {
return L10n.tr("Localizable", "a11y_react_with", String(describing: p1))
@@ -1048,6 +1052,10 @@ internal enum L10n {
}
/// You have new messages.
internal static var notificationFallbackContent: String { return L10n.tr("Localizable", "notification_fallback_content") }
/// Plural format key: "%#@COUNT@"
internal static func notificationFallbackNContent(_ p1: Int) -> String {
return L10n.tr("Localizable", "notification_fallback_n_content", p1)
}
/// 📹 Incoming call
internal static var notificationIncomingCall: String { return L10n.tr("Localizable", "notification_incoming_call") }
/// ** Failed to send - please open room

View File

@@ -0,0 +1,33 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import SwiftUI
struct ElementNavigationStack<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
#if DEBUG
if ProcessInfo.isRunningAccessibilityTests {
// Wrap in VStack to safely apply .id() since applying .id() directly to NavigationStack crashes on iOS 26
VStack(spacing: 0) {
NavigationStack {
content
}
}
} else {
NavigationStack {
content
}
}
#else
NavigationStack {
content
}
#endif
}
}

View File

@@ -33,6 +33,7 @@ struct EditRoomAddressListRow: View {
.font(.compound.bodyLG)
.foregroundStyle(.compound.textPrimary)
.padding(.horizontal, 8)
.accessibilityLabel(L10n.a11yAddress)
.accessibilityHint(L10n.a11yEditRoomAddressHint(fullAddress))
Text(":\(serverName)")
.font(.compound.bodyLG)

View File

@@ -55,7 +55,7 @@ struct JoinCallButton: View {
struct JoinCallButton_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ElementNavigationStack {
Color.clear
.toolbar {
ToolbarItem(placement: .confirmationAction) {

View File

@@ -71,7 +71,7 @@ struct ToolbarButton: View {
struct ToolbarButton_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
NavigationStack {
ElementNavigationStack {
Color.clear
.toolbar {
ToolbarItem(placement: .confirmationAction) {

View File

@@ -116,7 +116,7 @@ struct AppLockScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = AppLockScreenViewModel(appLockService: AppLockServiceMock.mock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
AppLockScreen(context: viewModel.context)
}
}

View File

@@ -77,12 +77,12 @@ struct AppLockSetupBiometricsScreen_Previews: PreviewProvider, TestablePreview {
static let touchIDViewModel = AppLockSetupBiometricsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .touchID))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
AppLockSetupBiometricsScreen(context: faceIDViewModel.context)
}
.previewDisplayName("Face ID")
NavigationStack {
ElementNavigationStack {
AppLockSetupBiometricsScreen(context: touchIDViewModel.context)
}
.previewDisplayName("Touch ID")

View File

@@ -107,22 +107,22 @@ struct AppLockSetupPINScreen_Previews: PreviewProvider, TestablePreview {
appLockService: failedService)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
AppLockSetupPINScreen(context: createViewModel.context)
}
.previewDisplayName("Create")
NavigationStack {
ElementNavigationStack {
AppLockSetupPINScreen(context: confirmViewModel.context)
}
.previewDisplayName("Confirm")
NavigationStack {
ElementNavigationStack {
AppLockSetupPINScreen(context: unlockViewModel.context)
}
.previewDisplayName("Unlock")
NavigationStack {
ElementNavigationStack {
AppLockSetupPINScreen(context: unlockFailedViewModel.context)
}
.previewDisplayName("Unlock Failed")

View File

@@ -51,17 +51,17 @@ struct AppLockSetupSettingsScreen_Previews: PreviewProvider, TestablePreview {
static let biometricsUnavailableViewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock(biometryType: .none))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
AppLockSetupSettingsScreen(context: faceIDViewModel.context)
}
.previewDisplayName("Face ID")
NavigationStack {
ElementNavigationStack {
AppLockSetupSettingsScreen(context: touchIDViewModel.context)
}
.previewDisplayName("Touch ID (Mandatory)")
NavigationStack {
ElementNavigationStack {
AppLockSetupSettingsScreen(context: biometricsUnavailableViewModel.context)
}
.previewDisplayName("PIN only")

View File

@@ -135,19 +135,19 @@ struct LoginScreen_Previews: PreviewProvider, TestablePreview {
static let unconfiguredViewModel = makeViewModel(homeserverAddress: "somethingtofailconfiguration")
static var previews: some View {
NavigationStack {
ElementNavigationStack {
LoginScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.homeserver.loginMode).map { $0 == .password })
.previewDisplayName("Initial State")
NavigationStack {
ElementNavigationStack {
LoginScreen(context: credentialsViewModel.context)
}
.snapshotPreferences(expect: credentialsViewModel.context.observe(\.viewState.homeserver.loginMode).map { $0 == .password })
.previewDisplayName("Credentials Entered")
NavigationStack {
ElementNavigationStack {
LoginScreen(context: unconfiguredViewModel.context)
}
.previewDisplayName("Unsupported")

View File

@@ -140,19 +140,19 @@ struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview {
static let pickerViewModel = makeViewModel(mode: .picker(["dept1.company.com", "dept2.company.com", "dept3.company.com"]), flow: .login)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
ServerConfirmationScreen(context: loginViewModel.context)
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Login")
NavigationStack {
ElementNavigationStack {
ServerConfirmationScreen(context: registerViewModel.context)
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Register")
NavigationStack {
ElementNavigationStack {
ServerConfirmationScreen(context: pickerViewModel.context)
.toolbar(.visible, for: .navigationBar)
}

View File

@@ -97,15 +97,15 @@ struct ServerSelection_Previews: PreviewProvider, TestablePreview {
static let invalidViewModel = makeViewModel(for: "thisisbad")
static var previews: some View {
NavigationStack {
ElementNavigationStack {
ServerSelectionScreen(context: matrixViewModel.context)
}
NavigationStack {
ElementNavigationStack {
ServerSelectionScreen(context: emptyViewModel.context)
}
NavigationStack {
ElementNavigationStack {
ServerSelectionScreen(context: invalidViewModel.context)
}
.snapshotPreferences(expect: invalidViewModel.context.observe(\.viewState.hasValidationError))

View File

@@ -169,7 +169,7 @@ struct SoftLogoutScreen_Previews: PreviewProvider, TestablePreview {
}
static func screen(for viewModel: SoftLogoutScreenViewModel) -> some View {
NavigationStack {
ElementNavigationStack {
SoftLogoutScreen(context: viewModel.context)
.navigationBarTitleDisplayMode(.inline)
.toolbar {

View File

@@ -61,7 +61,7 @@ struct BlockedUsersScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
BlockedUsersScreen(context: viewModel.context)
}
}

View File

@@ -155,7 +155,7 @@ struct BugReportScreen: View {
struct BugReportScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
NavigationStack {
ElementNavigationStack {
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(.init()),
clientProxy: clientProxy,
@@ -164,7 +164,7 @@ struct BugReportScreen_Previews: PreviewProvider, TestablePreview {
}
.previewDisplayName("Without Screenshot")
NavigationStack {
ElementNavigationStack {
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(.init()),
clientProxy: clientProxy,

View File

@@ -17,7 +17,7 @@ struct CallScreen: View {
@ObservedObject var context: CallScreenViewModel.Context
var body: some View {
NavigationStack {
ElementNavigationStack {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())

View File

@@ -12,7 +12,7 @@ struct ChatsSpaceFiltersScreen: View {
@Bindable var context: ChatsSpaceFiltersScreenViewModel.Context
var body: some View {
NavigationStack {
ElementNavigationStack {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(context.viewState.visibleFilters) { filter in

View File

@@ -213,12 +213,12 @@ struct PollFormScreen_Previews: PreviewProvider, TestablePreview {
createdByAccountOwner: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
PollFormScreen(context: viewModel.context)
}
.previewDisplayName("New")
NavigationStack {
ElementNavigationStack {
PollFormScreen(context: editViewModel.context)
}
.previewDisplayName("Edit")

View File

@@ -394,61 +394,61 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: viewModel.context)
}
.previewDisplayName("Create Room")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: avatarViewModel.context)
}
.previewDisplayName("Create Room with avatar")
.snapshotPreferences(expect: avatarViewModel.context.$viewState.map { $0.avatarImage != nil })
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: spaceViewModel.context)
}
.previewDisplayName("Create Space")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: spaceWithAvatarViewModel.context)
}
.previewDisplayName("Create Space with avatar")
.snapshotPreferences(expect: spaceWithAvatarViewModel.context.$viewState.map { $0.avatarImage != nil })
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: publicRoomViewModel.context)
}
.previewDisplayName("Create Public Room")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: askToJoinViewModel.context)
}
.previewDisplayName("Create Knockable Room")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: publicRoomInvalidAliasViewModel.context)
}
.snapshotPreferences(expect: publicRoomInvalidAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty })
.previewDisplayName("Create Public Room, invalid alias")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: publicRoomExistingAliasViewModel.context)
}
.snapshotPreferences(expect: publicRoomExistingAliasViewModel.context.$viewState.map { !$0.aliasErrors.isEmpty })
.previewDisplayName("Create Public Room, existing alias")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: selectedSpaceViewModel.context)
}
.previewDisplayName("Create Room with already selected Space")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: selectedSpaceWithListViewModel.context)
}
.previewDisplayName("Create Room with a selected Space from the list")
NavigationStack {
ElementNavigationStack {
CreateRoomScreen(context: selectedSpaceWithAskToJoinViewModel.context)
}
.previewDisplayName("Create Knockable Room with already selected Space")

View File

@@ -21,7 +21,7 @@ struct CreateRoomSpaceSelectionSheet: View {
}
var body: some View {
NavigationStack {
ElementNavigationStack {
List {
Section {
ListRow(label: .plain(title: L10n.screenCreateRoomSpaceSelectionNoSpaceOption),

View File

@@ -82,11 +82,11 @@ struct DeclineAndBlockScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
DeclineAndBlockScreen(context: viewModel.context)
}
.previewDisplayName("Default")
NavigationStack {
ElementNavigationStack {
DeclineAndBlockScreen(context: viewModel.context)
.onAppear {
viewModel.context.shouldReport = true

View File

@@ -86,17 +86,17 @@ struct EditRoomAddressScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
EditRoomAddressScreen(context: noAliasviewModel.context)
}
.previewDisplayName("No alias")
NavigationStack {
ElementNavigationStack {
EditRoomAddressScreen(context: aliasviewModel.context)
}
.previewDisplayName("With alias")
NavigationStack {
ElementNavigationStack {
EditRoomAddressScreen(context: invalidSymbolsViewModel.context)
}
.snapshotPreferences(expect: invalidSymbolsViewModel.context.$viewState.map { state in
@@ -104,7 +104,7 @@ struct EditRoomAddressScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Invalid symbols")
NavigationStack {
ElementNavigationStack {
EditRoomAddressScreen(context: alreadyExistingViewModel.context)
}
.snapshotPreferences(expect: alreadyExistingViewModel.context.$viewState.map { state in

View File

@@ -20,7 +20,7 @@ struct EmojiPickerScreen: View {
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
var body: some View {
NavigationStack {
ElementNavigationStack {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: minimumWidth))], spacing: 16) {
ForEach(context.viewState.categories) { category in

View File

@@ -73,7 +73,7 @@ struct EncryptionResetPasswordScreen_Previews: PreviewProvider, TestablePreview
static let passwordPublisher = PassthroughSubject<String, Never>()
static let viewModel = EncryptionResetPasswordScreenViewModel(passwordPublisher: passwordPublisher)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
EncryptionResetPasswordScreen(context: viewModel.context)
}
}

View File

@@ -94,7 +94,7 @@ struct EncryptionResetScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = EncryptionResetScreenViewModel(clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
EncryptionResetScreen(context: viewModel.context)
}
}

View File

@@ -210,7 +210,7 @@ struct GlobalSearchScreen_Previews: PreviewProvider, TestablePreview {
mediaProvider: MediaProviderMock(configuration: .init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
GlobalSearchScreen(context: viewModel.context)
}
}

View File

@@ -135,7 +135,7 @@ private struct OldBloomModifier: ViewModifier {
struct BloomModifier_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
NavigationStack {
ElementNavigationStack {
mockScreen
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
.searchable(text: .constant(""), placement: .navigationBarDrawer(displayMode: .always))
@@ -143,7 +143,7 @@ struct BloomModifier_Previews: PreviewProvider, TestablePreview {
}
.previewDisplayName("Chats")
NavigationStack {
ElementNavigationStack {
mockScreen
.navigationTitle(L10n.screenSpaceListTitle)
.toolbarBloom(hasSearchBar: false)

View File

@@ -171,7 +171,7 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview {
static let loadedViewModel = viewModel(.rooms)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
HomeScreen(context: loadingViewModel.context)
}
.snapshotPreferences(expect: loadedViewModel.context.$viewState.map { state in
@@ -179,7 +179,7 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Loading")
NavigationStack {
ElementNavigationStack {
HomeScreen(context: emptyViewModel.context)
}
.snapshotPreferences(expect: emptyViewModel.context.$viewState.map { state in
@@ -187,7 +187,7 @@ struct HomeScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Empty")
NavigationStack {
ElementNavigationStack {
HomeScreen(context: loadedViewModel.context)
}
.snapshotPreferences(expect: loadedViewModel.context.$viewState.map { state in

View File

@@ -151,13 +151,13 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
static let selectedViewModel = makeViewModel(hasSelection: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
InviteUsersScreen(context: viewModel.context)
}
.previewDisplayName("Suggestions")
.snapshotPreferences(expect: viewModel.context.$viewState.map { !$0.usersSection.users.isEmpty })
NavigationStack {
ElementNavigationStack {
InviteUsersScreen(context: searchingViewModel.context)
}
.previewDisplayName("Searching")
@@ -165,7 +165,7 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
$0.usersSection.type == .searchResult && !$0.usersSection.users.isEmpty
})
NavigationStack {
ElementNavigationStack {
InviteUsersScreen(context: selectedViewModel.context)
}
.previewDisplayName("Selected")

View File

@@ -485,7 +485,7 @@ struct JoinRoomScreenPreviewWrapper: Identifiable {
let previewDisplayName = customPreviewName ?? previewDisplayName
let previewDisplayNameSuffix = isSpace ? " Space" : ""
if mode == .forbidden {
NavigationStack {
ElementNavigationStack {
JoinRoomScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
@@ -496,7 +496,7 @@ struct JoinRoomScreenPreviewWrapper: Identifiable {
}
.previewDisplayName(previewDisplayName + previewDisplayNameSuffix)
} else {
NavigationStack {
ElementNavigationStack {
JoinRoomScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in

View File

@@ -125,14 +125,14 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
]))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
KnockRequestsListScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
state.shouldDisplayRequests == true
})
NavigationStack {
ElementNavigationStack {
KnockRequestsListScreen(context: singleRequestViewModel.context)
}
.snapshotPreferences(expect: singleRequestViewModel.context.$viewState.map { state in
@@ -140,7 +140,7 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Single Request")
NavigationStack {
ElementNavigationStack {
KnockRequestsListScreen(context: emptyViewModel.context)
}
.snapshotPreferences(expect: emptyViewModel.context.$viewState.map { state in
@@ -148,7 +148,7 @@ struct KnockRequestsListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Empty state")
NavigationStack {
ElementNavigationStack {
KnockRequestsListScreen(context: loadingViewModel.context)
}
.snapshotPreferences(expect: loadingViewModel.context.$viewState.map { state in

View File

@@ -70,7 +70,7 @@ struct LabsScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
LabsScreen(context: viewModel.context)
}
}

View File

@@ -110,30 +110,30 @@ struct LinkNewDeviceScreen_Previews: PreviewProvider, TestablePreview {
static let unknownErrorViewModel = makeViewModel(mode: .error(.unknown))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
LinkNewDeviceScreen(context: viewModel.context)
}
.previewDisplayName("Ready")
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: false) })
NavigationStack {
ElementNavigationStack {
LinkNewDeviceScreen(context: generatingViewModel.context)
}
.previewDisplayName("Generating")
.snapshotPreferences(expect: generatingViewModel.context.observe(\.viewState.mode).map { $0 == .readyToLink(isGeneratingCode: true) })
NavigationStack {
ElementNavigationStack {
LinkNewDeviceScreen(context: loadingViewModel.context)
}
.previewDisplayName("Loading")
NavigationStack {
ElementNavigationStack {
LinkNewDeviceScreen(context: unsupportedViewModel.context)
}
.previewDisplayName("Unsupported")
.snapshotPreferences(expect: unsupportedViewModel.context.observe(\.viewState.mode).map { $0 == .error(.notSupported) })
NavigationStack {
ElementNavigationStack {
LinkNewDeviceScreen(context: unknownErrorViewModel.context)
}
.previewDisplayName("Unknown error")

View File

@@ -176,17 +176,17 @@ struct StaticLocationScreenViewer_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
StaticLocationScreen(context: pickerViewModel.context)
}
.previewDisplayName("Picker")
NavigationStack {
ElementNavigationStack {
StaticLocationScreen(context: viewModel.context)
}
.previewDisplayName("View Only")
NavigationStack {
ElementNavigationStack {
StaticLocationScreen(context: descriptionViewModel.context)
}
.previewDisplayName("View Only (with description)")

View File

@@ -112,7 +112,7 @@ struct ManageAuthorizedSpacesScreen_Previews: PreviewProvider, TestablePreview {
mediaProvider: MediaProviderMock(configuration: .init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
ManageAuthorizedSpacesScreen(context: viewModel.context)
}
}

View File

@@ -272,22 +272,22 @@ struct MediaEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
static let emptyFilesViewModel = makeViewModel(empty: true, screenMode: .files)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
MediaEventsTimelineScreen(context: mediaViewModel.context)
}
.previewDisplayName("Media")
NavigationStack {
ElementNavigationStack {
MediaEventsTimelineScreen(context: filesViewModel.context)
}
.previewDisplayName("Files")
NavigationStack {
ElementNavigationStack {
MediaEventsTimelineScreen(context: emptyMediaViewModel.context)
}
.previewDisplayName("Empty Media")
NavigationStack {
ElementNavigationStack {
MediaEventsTimelineScreen(context: emptyFilesViewModel.context)
}
.previewDisplayName("Empty Files")

View File

@@ -280,7 +280,7 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock.default)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
MediaUploadPreviewScreen(context: viewModel.context)
}

View File

@@ -102,7 +102,7 @@ struct MessageForwardingScreen_Previews: PreviewProvider, TestablePreview {
roomSummaryProvider: summaryProvider,
userIndicatorController: UserIndicatorControllerMock())
NavigationStack {
ElementNavigationStack {
MessageForwardingScreen(context: viewModel.context)
}
}

View File

@@ -119,7 +119,7 @@ struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview {
static var loadingViewModel = makeViewModel(recoveryState: .unknown)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
IdentityConfirmationScreen(context: viewModel.context)
}
.previewDisplayName("Actions")
@@ -127,7 +127,7 @@ struct IdentityConfirmationScreen_Previews: PreviewProvider, TestablePreview {
actions?.contains([.interactiveVerification, .recovery]) == true
})
NavigationStack {
ElementNavigationStack {
IdentityConfirmationScreen(context: loadingViewModel.context)
}
.previewDisplayName("Loading")

View File

@@ -58,7 +58,7 @@ struct IdentityConfirmedScreen: View {
struct IdentityConfirmedScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = IdentityConfirmedScreenViewModel()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
IdentityConfirmedScreen(context: viewModel.context)
}
}

View File

@@ -88,7 +88,7 @@ struct PinnedEventsTimelineScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
PinnedEventsTimelineScreen(context: viewModel.context, timelineContext: emptyTimelineViewModel.context)
}
.previewDisplayName("Empty")

View File

@@ -171,14 +171,14 @@ struct QRCodeErrorView: View {
struct QRCodeErrorView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
ForEach(QRCodeLoginState.ErrorState.allCases, id: \.self) { errorState in
NavigationStack {
ElementNavigationStack {
QRCodeErrorView(errorState: errorState, canSignInManually: true) { _ in }
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName(errorState.previewDisplayName)
}
NavigationStack {
ElementNavigationStack {
QRCodeErrorView(errorState: .linkingNotSupported, canSignInManually: false) { _ in }
.toolbar(.visible, for: .navigationBar)
}

View File

@@ -219,6 +219,7 @@ struct QRCodeLoginScreen: View {
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.accessibilityLabel(L10n.a11yQrCode)
SFNumberedListView(items: context.viewState.instructions.linkMobileItems)
}
@@ -350,38 +351,38 @@ struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
static let errorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.declined))
static var previews: some View {
NavigationStack { QRCodeLoginScreen(context: loginInstructionsStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: loginInstructionsStateViewModel.context) }
.previewDisplayName("Login instructions")
NavigationStack { QRCodeLoginScreen(context: linkInstructionsStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: linkInstructionsStateViewModel.context) }
.previewDisplayName("Link instructions")
NavigationStack { QRCodeLoginScreen(context: scanningStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: scanningStateViewModel.context) }
.previewDisplayName("Scanning")
NavigationStack { QRCodeLoginScreen(context: connectingStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: connectingStateViewModel.context) }
.previewDisplayName("Connecting")
NavigationStack { QRCodeLoginScreen(context: invalidStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: invalidStateViewModel.context) }
.previewDisplayName("Invalid")
NavigationStack { QRCodeLoginScreen(context: notAllowedStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: notAllowedStateViewModel.context) }
.previewDisplayName("Not allowed")
NavigationStack { QRCodeLoginScreen(context: deviceNotSignedInStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: deviceNotSignedInStateViewModel.context) }
.previewDisplayName("Device not signed in")
NavigationStack { QRCodeLoginScreen(context: showingStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: showingStateViewModel.context) }
.previewDisplayName("Showing")
NavigationStack { QRCodeLoginScreen(context: deviceCodeStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: deviceCodeStateViewModel.context) }
.previewDisplayName("Device code")
NavigationStack { QRCodeLoginScreen(context: verificationCodeStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: verificationCodeStateViewModel.context) }
.previewDisplayName("Verification code")
NavigationStack { QRCodeLoginScreen(context: confirmCodeStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeStateViewModel.context) }
.previewDisplayName("Confirm code")
NavigationStack { QRCodeLoginScreen(context: confirmCodeEnteredStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeEnteredStateViewModel.context) }
.previewDisplayName("Confirm code entered")
NavigationStack { QRCodeLoginScreen(context: confirmCodeInvalidStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeInvalidStateViewModel.context) }
.previewDisplayName("Confirm code invalid")
NavigationStack { QRCodeLoginScreen(context: errorStateViewModel.context) }
ElementNavigationStack { QRCodeLoginScreen(context: errorStateViewModel.context) }
.previewDisplayName("Error")
}
}

View File

@@ -73,7 +73,7 @@ struct ReportContentScreen_Previews: PreviewProvider, TestablePreview {
clientProxy: ClientProxyMock(.init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
ReportContentScreen(context: viewModel.context)
}
}

View File

@@ -65,7 +65,7 @@ struct ReportRoomScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = ReportRoomScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
ReportRoomScreen(context: viewModel.context)
}
}

View File

@@ -70,17 +70,17 @@ struct RoomChangePermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let spaceViewModel = makeViewModel(isSpace: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomChangePermissionsScreen(context: roomViewModel.context)
}
.previewDisplayName("Room")
NavigationStack {
ElementNavigationStack {
RoomChangePermissionsScreen(context: roomAsUserViewModel.context)
}
.previewDisplayName("Room as User")
NavigationStack {
ElementNavigationStack {
RoomChangePermissionsScreen(context: spaceViewModel.context)
}
.previewDisplayName("Space")

View File

@@ -118,22 +118,22 @@ struct RoomChangeRolesScreen_Previews: PreviewProvider, TestablePreview {
static let moderatorViewModel = makeViewModel(mode: .moderator, ownRole: .administrator)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomChangeRolesScreen(context: ownerViewModel.context)
}
.previewDisplayName("Owners")
NavigationStack {
ElementNavigationStack {
RoomChangeRolesScreen(context: administratorOrOwnerViewModel.context)
}
.previewDisplayName("Administrator or Owners")
NavigationStack {
ElementNavigationStack {
RoomChangeRolesScreen(context: administratorViewModel.context)
}
.previewDisplayName("Administrators")
NavigationStack {
ElementNavigationStack {
RoomChangeRolesScreen(context: moderatorViewModel.context)
}
.previewDisplayName("Moderators")

View File

@@ -175,12 +175,12 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomDetailsEditScreen(context: readOnlyViewModel.context)
}
.previewDisplayName("Read only")
NavigationStack {
ElementNavigationStack {
RoomDetailsEditScreen(context: editableViewModel.context)
}
.snapshotPreferences(expect: editableViewModel.context.$viewState.map { state in

View File

@@ -13,7 +13,7 @@ struct RoomDirectorySearchScreen: View {
@ObservedObject var context: RoomDirectorySearchScreenViewModel.Context
var body: some View {
NavigationStack {
ElementNavigationStack {
List {
Section {
ForEach(context.viewState.rooms) { room in

View File

@@ -173,7 +173,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
static let emptyBannedViewModel = makeViewModel(withBanned: false, isAdmin: false, initialMode: .members)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomMembersListScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in
@@ -181,7 +181,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Member")
NavigationStack {
ElementNavigationStack {
RoomMembersListScreen(context: invitesViewModel.context)
}
.snapshotPreferences(expect: invitesViewModel.context.$viewState.map { state in
@@ -189,7 +189,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Invites")
NavigationStack {
ElementNavigationStack {
RoomMembersListScreen(context: adminViewModel.context)
}
.snapshotPreferences(expect: adminViewModel.context.$viewState.map { state in
@@ -197,7 +197,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Admin: Members")
NavigationStack {
ElementNavigationStack {
RoomMembersListScreen(context: bannedViewModel.context)
}
.snapshotPreferences(expect: bannedViewModel.context.$viewState.map { state in
@@ -205,7 +205,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Admin: Banned")
NavigationStack {
ElementNavigationStack {
RoomMembersListScreen(context: emptyBannedViewModel.context)
.onAppear { emptyBannedViewModel.context.searchQuery = "Dan" }
}

View File

@@ -145,12 +145,12 @@ struct RoomPollsHistoryScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomPollsHistoryScreen(context: viewModelEmpty.context)
}
.previewDisplayName("No polls")
NavigationStack {
ElementNavigationStack {
RoomPollsHistoryScreen(context: viewModel.context)
}
.previewDisplayName("polls")

View File

@@ -128,12 +128,12 @@ struct RoomRolesAndPermissionsScreen_Previews: PreviewProvider, TestablePreview
userIndicatorController: UserIndicatorControllerMock(),
analytics: ServiceLocator.shared.analytics)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomRolesAndPermissionsScreen(context: viewModel.context)
}
.previewDisplayName("Admin")
NavigationStack {
ElementNavigationStack {
RoomRolesAndPermissionsScreen(context: creatorViewModel.context)
}
.previewDisplayName("Creator")

View File

@@ -201,14 +201,14 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
static let tombstonedViewModels = makeViewModels(hasSuccessor: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomScreen(context: viewModels.room.context,
timelineContext: viewModels.timeline.context,
composerToolbar: ComposerToolbar.mock())
}
.previewDisplayName("Normal")
NavigationStack {
ElementNavigationStack {
RoomScreen(context: readOnlyViewModels.room.context,
timelineContext: readOnlyViewModels.timeline.context,
composerToolbar: ComposerToolbar.mock())
@@ -216,7 +216,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("Read-only")
.snapshotPreferences(expect: readOnlyViewModels.room.context.$viewState.map { !$0.canSendMessage })
NavigationStack {
ElementNavigationStack {
RoomScreen(context: tombstonedViewModels.room.context,
timelineContext: tombstonedViewModels.timeline.context,
composerToolbar: ComposerToolbar.mock())

View File

@@ -171,7 +171,7 @@ struct SwipeRightAction_Previews: PreviewProvider, TestablePreview {
@State private var isPresentingSheet = false
var body: some View {
NavigationStack {
ElementNavigationStack {
ScrollView {
VStack(alignment: .leading, spacing: 2) {
Text("This is a message from somebody with a couple of lines of text.")

View File

@@ -98,7 +98,7 @@ struct RoomSelectionScreen_Previews: PreviewProvider, TestablePreview {
let viewModel = RoomSelectionScreenViewModel(userSession: UserSessionMock(.init()),
roomSummaryProvider: summaryProvider)
NavigationStack {
ElementNavigationStack {
RoomSelectionScreen(context: viewModel.context)
}
}

View File

@@ -90,7 +90,7 @@ struct SecureBackupKeyBackupScreen_Previews: PreviewProvider, TestablePreview {
static let setupViewModel = viewModel(keyBackupState: .enabled)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SecureBackupKeyBackupScreen(context: setupViewModel.context)
}
.previewDisplayName("Set up")

View File

@@ -117,31 +117,31 @@ struct SecureBackupLogoutConfirmationScreen_Previews: PreviewProvider, TestableP
static let offlineViewModel = makeViewModel(mode: .offline)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SecureBackupLogoutConfirmationScreen(context: viewModel.context)
}
.previewDisplayName("Confirmation")
NavigationStack {
ElementNavigationStack {
SecureBackupLogoutConfirmationScreen(context: waitingViewModel.context)
}
.previewDisplayName("Waiting")
.snapshotPreferences(expect: waitingViewModel.context.observe(\.viewState.mode).map { $0 == .waitingToStart(hasStalled: false) })
NavigationStack {
ElementNavigationStack {
SecureBackupLogoutConfirmationScreen(context: ongoingViewModel.context)
}
.previewDisplayName("Ongoing")
.snapshotPreferences(expect: ongoingViewModel.context.observe(\.viewState.mode).map { $0 == .backupOngoing(progress: 0.5) })
// Uses the same view model as Waiting but with a different expectation.
NavigationStack {
ElementNavigationStack {
SecureBackupLogoutConfirmationScreen(context: waitingViewModel.context)
}
.previewDisplayName("Stalled")
.snapshotPreferences(expect: waitingViewModel.context.observe(\.viewState.mode).map { $0 == .waitingToStart(hasStalled: true) })
NavigationStack {
ElementNavigationStack {
SecureBackupLogoutConfirmationScreen(context: offlineViewModel.context)
}
.previewDisplayName("Offline")

View File

@@ -246,28 +246,28 @@ struct SecureBackupRecoveryKeyScreen_Previews: PreviewProvider, TestablePreview
static let unknownViewModel = viewModel(recoveryState: .unknown)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SecureBackupRecoveryKeyScreen(context: notSetUpViewModel.context)
}
.previewDisplayName("Not set up")
NavigationStack {
ElementNavigationStack {
SecureBackupRecoveryKeyScreen(context: generatingViewModel.context)
}
.previewDisplayName("Generating")
NavigationStack {
ElementNavigationStack {
SecureBackupRecoveryKeyScreen(context: setupViewModel.context)
}
.snapshotPreferences(expect: setupViewModel.context.observe(\.viewState.recoveryKey).map { $0 != nil })
.previewDisplayName("Set up")
NavigationStack {
ElementNavigationStack {
SecureBackupRecoveryKeyScreen(context: incompleteViewModel.context)
}
.previewDisplayName("Incomplete")
NavigationStack {
ElementNavigationStack {
SecureBackupRecoveryKeyScreen(context: unknownViewModel.context)
}
.previewDisplayName("Unknown")

View File

@@ -123,25 +123,25 @@ struct SecureBackupScreen_Previews: PreviewProvider, TestablePreview {
static let recoveryIncompleteViewModel = viewModel(keyBackupState: .enabled, recoveryState: .incomplete)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SecureBackupScreen(context: bothSetupViewModel.context)
}
.snapshotPreferences(expect: bothSetupViewModel.context.observe(\.viewState.keyBackupState).map { $0 == .enabled })
.previewDisplayName("Both setup")
NavigationStack {
ElementNavigationStack {
SecureBackupScreen(context: onlyKeyBackupSetUpViewModel.context)
}
.snapshotPreferences(expect: onlyKeyBackupSetUpViewModel.context.observe(\.viewState.keyBackupState).map { $0 == .enabled })
.previewDisplayName("Only key backup setup")
NavigationStack {
ElementNavigationStack {
SecureBackupScreen(context: keyBackupDisabledViewModel.context)
}
.snapshotPreferences(expect: keyBackupDisabledViewModel.context.observe(\.viewState.keyBackupState).map { $0 == .unknown })
.previewDisplayName("Key backup disabled")
NavigationStack {
ElementNavigationStack {
SecureBackupScreen(context: recoveryIncompleteViewModel.context)
}
.snapshotPreferences(expect: recoveryIncompleteViewModel.context.observe(\.viewState.recoveryState).map { $0 == .incomplete })

View File

@@ -359,12 +359,12 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: inviteOnlyViewModel.context)
}
.previewDisplayName("Private invite only room")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: publicViewModel.context)
}
.snapshotPreferences(expect: publicViewModel.context.$viewState.map { state in
@@ -372,12 +372,12 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Public room")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: publicNoAddressViewModel.context)
}
.previewDisplayName("Public room without address")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: singleSpaceMembersViewModel.context)
}
.snapshotPreferences(expect: singleSpaceMembersViewModel.context.$viewState.map { state in
@@ -385,7 +385,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Space members")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: multipleSpacesMembersViewModel.context)
}
.snapshotPreferences(expect: multipleSpacesMembersViewModel.context.$viewState.map { state in
@@ -393,7 +393,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Multiple Spaces members")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: askToJoinViewModel.context)
}
.snapshotPreferences(expect: askToJoinViewModel.context.$viewState.map { state in
@@ -401,7 +401,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Ask to join room")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: singleAskToJoinSpaceMembersViewModel.context)
}
.snapshotPreferences(expect: singleAskToJoinSpaceMembersViewModel.context.$viewState.map { state in
@@ -409,7 +409,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Ask to join with single space members room")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: multipleAskToJoinSpacesMembersViewModel.context)
}
.snapshotPreferences(expect: multipleAskToJoinSpacesMembersViewModel.context.$viewState.map { state in
@@ -417,7 +417,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
})
.previewDisplayName("Ask to join with multiple spaces members room")
NavigationStack {
ElementNavigationStack {
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
}
.snapshotPreferences(expect: publicSpaceViewModel.context.$viewState.map { state in

View File

@@ -107,7 +107,7 @@ struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview {
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
AdvancedSettingsScreen(context: viewModel.context)
}
}

View File

@@ -96,7 +96,7 @@ struct DeactivateAccountScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = DeactivateAccountScreenViewModel(clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ElementNavigationStack {
DeactivateAccountScreen(context: viewModel.context)
}
}

View File

@@ -215,7 +215,7 @@ struct DeveloperOptionsScreen_Previews: PreviewProvider {
appHooks: AppHooks(),
clientProxy: ClientProxyMock(.init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
DeveloperOptionsScreen(context: viewModel.context)
}
}

View File

@@ -267,13 +267,13 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
static let bugReportDisabledViewModel = makeViewModel(isBugReportServiceEnabled: false)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SettingsScreen(context: viewModel.context)
}
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.accountSessionsListURL).map { $0 != nil })
.previewDisplayName("Default")
NavigationStack {
ElementNavigationStack {
SettingsScreen(context: bugReportDisabledViewModel.context)
}
.snapshotPreferences(expect: bugReportDisabledViewModel.context.observe(\.viewState.accountSessionsListURL).map { $0 != nil })

View File

@@ -131,7 +131,7 @@ struct UserDetailsEditScreen_Previews: PreviewProvider, TestablePreview {
userIndicatorController: UserIndicatorControllerMock.default)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
UserDetailsEditScreen(context: viewModel.context)
}
}

View File

@@ -164,7 +164,7 @@ struct SpaceAddRoomsScreen_Previews: PreviewProvider, TestablePreview {
static let selectedViewModel = makeViewModel(searchQuery: "Foundation", hasSelection: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SpaceAddRoomsScreen(context: viewModel.context)
}
.previewDisplayName("Suggested")
@@ -172,12 +172,12 @@ struct SpaceAddRoomsScreen_Previews: PreviewProvider, TestablePreview {
$0.type == .suggestions && !$0.rooms.isEmpty
})
NavigationStack {
ElementNavigationStack {
SpaceAddRoomsScreen(context: searchingViewModel.context)
}
.previewDisplayName("Searching")
NavigationStack {
ElementNavigationStack {
SpaceAddRoomsScreen(context: selectedViewModel.context)
}
.previewDisplayName("Selected")

View File

@@ -194,16 +194,16 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
static let newSpaceViewModel = makeViewModel(isNewSpace: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SpaceScreen(context: viewModel.context)
}
NavigationStack {
ElementNavigationStack {
SpaceScreen(context: managingViewModel.context)
}
.previewDisplayName("Managing")
NavigationStack {
ElementNavigationStack {
SpaceScreen(context: newSpaceViewModel.context)
}
.previewDisplayName("New Space")

View File

@@ -146,12 +146,12 @@ struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview {
appSettings: ServiceLocator.shared.settings)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SpaceSettingsScreen(context: ownerViewModel.context)
}
.previewDisplayName("Owner")
NavigationStack {
ElementNavigationStack {
SpaceSettingsScreen(context: userViewModel.context)
}
.previewDisplayName("User")

View File

@@ -139,11 +139,11 @@ struct SpacesScreen_Previews: PreviewProvider, TestablePreview {
static let emptyViewModel = makeViewModel(isEmpty: true)
static var previews: some View {
NavigationStack {
ElementNavigationStack {
SpacesScreen(context: viewModel.context)
}
NavigationStack {
ElementNavigationStack {
SpacesScreen(context: emptyViewModel.context)
}
.previewDisplayName("Empty")

View File

@@ -168,7 +168,7 @@ struct StartChatScreen_Previews: PreviewProvider, TestablePreview {
}()
static var previews: some View {
NavigationStack {
ElementNavigationStack {
StartChatScreen(context: viewModel.context)
}
}

View File

@@ -84,7 +84,7 @@ struct LongPressWithFeedback_Previews: PreviewProvider, TestablePreview {
@State private var isPresentingSheet = false
var body: some View {
NavigationStack {
ElementNavigationStack {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
mockBubble("This is a message from somebody with a couple of lines of text.")

View File

@@ -14,7 +14,7 @@ struct TimelineItemDebugView: View {
let info: TimelineItemDebugInfo
var body: some View {
NavigationStack {
ElementNavigationStack {
ScrollView {
VStack(spacing: 8) {
TimelineItemInfoDisclosureGroup(title: "Model", text: info.model)

View File

@@ -102,7 +102,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomScreen(context: roomViewModel.context,
timelineContext: timelineViewModel.context,
composerToolbar: ComposerToolbar.mock())

View File

@@ -156,7 +156,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
static var previews: some View {
NavigationStack {
ElementNavigationStack {
RoomScreen(context: roomViewModel.context,
timelineContext: timelineViewModel.context,
composerToolbar: ComposerToolbar.mock())

View File

@@ -98,7 +98,7 @@ lane :accessibility_tests do |options|
run_tests(
scheme: "AccessibilityTests",
device: "iPhone 16 (18.5)", # The tests are randomly crashing on iOS 26
device: "iPhone 17 (#{simulator_version})",
ensure_devices_found: true,
prelaunch_simulator: false,
result_bundle: true,
@@ -338,4 +338,4 @@ private_lane :create_simulator_if_necessary do |options|
end
# device_id is unused right now but is useful to check e.g. the boot status of a simulator.
end
end