Fixed a silly message variant bug, unit test env var tweaks

• Added an env variable to enable the debug disappearing message durations
• Moved the 'processUnitTestEnvVariablesIfNeeded' function into a separate file and added some docs (so we can just look at that file to see what is supported)
• Fixed an issue where the deleted message artifact variant had incorrectly gotten it's value changed (too late to change it back so need a migration)
pull/1061/head
Morgan Pretty 1 month ago
parent 25f3e836ef
commit 828b25254a

@ -811,6 +811,8 @@
FD848B9628422A2A000E298B /* MessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B86283B844B000E298B /* MessageViewModel.swift */; };
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9728422F1A000E298B /* Date+Utilities.swift */; };
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */; };
FD860CBE2D6E7DAA00BBE29C /* DeveloperSettingsViewModel+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */; };
FD86FDA32BC5020600EC251B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */; };
FD86FDA42BC51C5400EC251B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */; };
FD86FDA52BC51C5500EC251B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */; };
@ -1997,6 +1999,8 @@
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _024_FixBustedInteractionVariant.swift; sourceTree = "<group>"; };
FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperSettingsViewModel+Testing.swift"; sourceTree = "<group>"; };
FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = "<group>"; };
FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = "<group>"; };
@ -3207,9 +3211,9 @@
C360969125AD1765008B62B2 /* Settings */ = {
isa = PBXGroup;
children = (
FD37E9CD28A1E682003AE748 /* Views */,
9422569A2C23F8F000C0FDBF /* QRCodeScreen.swift */,
9422569B2C23F8F000C0FDBF /* RecoveryPasswordScreen.swift */,
FD37E9CD28A1E682003AE748 /* Views */,
FD71162D28E168C700B47552 /* SettingsViewModel.swift */,
FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */,
FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */,
@ -3221,6 +3225,7 @@
FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */,
B86BD08523399CEF000F5AE3 /* SeedModal.swift */,
FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */,
FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */,
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */,
FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */,
);
@ -3801,6 +3806,7 @@
FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */,
FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */,
FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */,
FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */,
);
path = Migrations;
sourceTree = "<group>";
@ -6234,6 +6240,7 @@
7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */,
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */,
FD2286692C37DA5500BC06F7 /* PollerType.swift in Sources */,
FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */,
FD22727B2C32911C004D8A6C /* MessageSendJob.swift in Sources */,
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */,
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
@ -6357,6 +6364,7 @@
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */,
FDE754FA2C9BB0B0002A2623 /* NotificationPresenter.swift in Sources */,
FD860CBE2D6E7DAA00BBE29C /* DeveloperSettingsViewModel+Testing.swift in Sources */,
FDE125232A837E4E002DA685 /* MainAppContext.swift in Sources */,
7B9F71D12852EEE2006DFE7B /* EmojiWithSkinTones+String.swift in Sources */,
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */,
@ -7907,7 +7915,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 546;
CURRENT_PROJECT_VERSION = 547;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -7983,7 +7991,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 546;
CURRENT_PROJECT_VERSION = 547;
ENABLE_BITCODE = NO;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;

@ -176,12 +176,13 @@ extension ContextMenuVC {
using dependencies: Dependencies
) -> [Action]? {
switch cellViewModel.variant {
case .standardIncomingDeleted, .standardIncomingDeletedLocally, .standardOutgoingDeleted,
.standardOutgoingDeletedLocally, .infoCall, .infoScreenshotNotification, .infoMediaSavedNotification,
.infoLegacyGroupCreated, .infoLegacyGroupUpdated, .infoLegacyGroupCurrentUserLeft,
.infoGroupCurrentUserLeaving, .infoGroupCurrentUserErrorLeaving,
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate, .infoGroupInfoInvited,
.infoGroupInfoUpdated, .infoGroupMembersUpdated:
case ._legacyStandardIncomingDeleted, .standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally, .infoCall,
.infoScreenshotNotification, .infoMediaSavedNotification, .infoLegacyGroupCreated,
.infoLegacyGroupUpdated, .infoLegacyGroupCurrentUserLeft, .infoGroupCurrentUserLeaving,
.infoGroupCurrentUserErrorLeaving, .infoMessageRequestAccepted,
.infoDisappearingMessagesUpdate, .infoGroupInfoInvited, .infoGroupInfoUpdated,
.infoGroupMembersUpdated:
// Let the user delete info messages and unsent messages
return [ Action.delete(cellViewModel, delegate) ]

@ -78,8 +78,9 @@ public class MessageCell: UITableViewCell {
guard viewModel.cellType != .unreadMarker else { return UnreadMarkerCell.self }
switch viewModel.variant {
case .standardOutgoing, .standardIncoming, .standardIncomingDeleted, .standardOutgoingDeleted,
.standardIncomingDeletedLocally, .standardOutgoingDeletedLocally:
case .standardOutgoing, .standardIncoming, ._legacyStandardIncomingDeleted,
.standardIncomingDeleted, .standardOutgoingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeletedLocally:
return VisibleMessageCell.self
case .infoLegacyGroupCreated, .infoLegacyGroupUpdated, .infoLegacyGroupCurrentUserLeft,

@ -0,0 +1,90 @@
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable
import UIKit
import SessionUtilitiesKit
// MARK: - Automated Test Convenience
extension DeveloperSettingsViewModel {
/// Processes and sets feature flags based on environment variables when running in the iOS simulator to allow extenrally
/// triggered automated tests to start in a specific state or with specific features enabled
///
/// In order to use these with Appium (a UI testing framework used internally) these settings can be added to the device
/// configuration as below, where the name of the value should match exactly to the `EnvironmentVariable` value
/// below and the value should match one of the options documented below
/// ```
/// const iOSCapabilities: AppiumXCUITestCapabilities = {
/// 'appium:processArguments': {
/// env: {
/// 'serviceNetwork': "testnet",
/// 'debugDisappearingMessageDurations': true
/// }
/// }
/// }
/// ```
static func processUnitTestEnvVariablesIfNeeded(using dependencies: Dependencies) {
#if targetEnvironment(simulator)
enum EnvironmentVariable: String {
/// Disables animations for the app (where possible)
///
/// **Value:** `true`/`false` (default: `true`)
case animationsEnabled
/// Controls whether the "keys" for strings should be displayed instead of their localized values
///
/// **Value:** `true`/`false` (default: `false`)
case showStringKeys
/// Controls whether the app communicates with mainnet or testnet by default
///
/// **Value:** `"mainnet"`/`"testnet"` (default: `"mainnet"`)
case serviceNetwork
/// Controls whether the app should trigger it's "Force Offline" behaviour (the network doesn't connect and all requests
/// fail after a 1 second delay with a serviceUnavailable error)
///
/// **Value:** `true`/`false` (default: `false`)
case forceOffline
/// Controls whether the app should offer the debug durations for disappearing messages (eg. `10s`, `30s`, etc.)
///
/// **Value:** `true`/`false` (default: `false`)
case debugDisappearingMessageDurations
}
ProcessInfo.processInfo.environment.forEach { key, value in
guard let variable: EnvironmentVariable = EnvironmentVariable(rawValue: key) else { return }
switch variable {
case .animationsEnabled:
dependencies.set(feature: .animationsEnabled, to: (value == "true"))
guard value == "false" else { return }
UIView.setAnimationsEnabled(false)
case .showStringKeys:
dependencies.set(feature: .showStringKeys, to: (value == "true"))
case .serviceNetwork:
let network: ServiceNetwork
switch value {
case "testnet": network = .testnet
default: network = .mainnet
}
DeveloperSettingsViewModel.updateServiceNetwork(to: network, using: dependencies)
case .forceOffline:
dependencies.set(feature: .forceOffline, to: (value == "true"))
case .debugDisappearingMessageDurations:
dependencies.set(feature: .debugDisappearingMessageDurations, to: (value == "true"))
}
}
#endif
}
}

@ -889,7 +889,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
.sinkUntilComplete()
}
private static func updateServiceNetwork(
internal static func updateServiceNetwork(
to updatedNetwork: ServiceNetwork?,
using dependencies: Dependencies
) {
@ -1434,51 +1434,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
}
}
// MARK: - Automated Test Convenience
extension DeveloperSettingsViewModel {
static func processUnitTestEnvVariablesIfNeeded(using dependencies: Dependencies) {
#if targetEnvironment(simulator)
enum EnvironmentVariable: String {
case animationsEnabled
case showStringKeys
case serviceNetwork
case forceOffline
}
ProcessInfo.processInfo.environment.forEach { key, value in
guard let variable: EnvironmentVariable = EnvironmentVariable(rawValue: key) else { return }
switch variable {
case .animationsEnabled:
dependencies.set(feature: .animationsEnabled, to: (value == "true"))
guard value == "false" else { return }
UIView.setAnimationsEnabled(false)
case .showStringKeys:
dependencies.set(feature: .showStringKeys, to: (value == "true"))
case .serviceNetwork:
let network: ServiceNetwork
switch value {
case "testnet": network = .testnet
default: network = .mainnet
}
DeveloperSettingsViewModel.updateServiceNetwork(to: network, using: dependencies)
case .forceOffline:
dependencies.set(feature: .forceOffline, to: (value == "true"))
}
}
#endif
}
}
// MARK: - DocumentPickerResult
private class DocumentPickerResult: NSObject, UIDocumentPickerDelegate {

@ -40,7 +40,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
_020_AddMissingWhisperFlag.self,
_021_ReworkRecipientState.self,
_022_GroupsRebuildChanges.self,
_023_GroupsExpiredFlag.self
_023_GroupsExpiredFlag.self,
_024_FixBustedInteractionVariant.self
]
]
)

@ -1,9 +1,7 @@
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import UIKit.UIImage
import GRDB
import SessionSnodeKit
import SessionUtilitiesKit
enum _023_GroupsExpiredFlag: Migration {

@ -0,0 +1,26 @@
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
/// There was a bug with internal releases of the Groups Rebuild feature where we incorrectly assigned an `Interaction.Variant`
/// value of `3` to deleted message artifacts when it should have been `2`, this migration updates any interactions with a value of `2`
/// to be `3`
enum _024_FixBustedInteractionVariant: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "FixBustedInteractionVariant"
static let minExpectedRunDuration: TimeInterval = 0.1
static var createdTables: [(FetchableRecord & TableRecord).Type] = []
static func migrate(_ db: Database, using dependencies: Dependencies) throws {
try db.execute(sql: """
UPDATE interaction
SET variant = \(Interaction.Variant.standardIncomingDeleted.rawValue)
WHERE variant = \(Interaction.Variant._legacyStandardIncomingDeleted.rawValue)
""")
Storage.update(progress: 1, for: self, in: target, using: dependencies)
}
}

@ -158,8 +158,9 @@ internal extension ControlMessageProcessRecord {
using dependencies: Dependencies
) {
switch variant {
case .standardOutgoing, .standardIncoming, .standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally, .infoLegacyGroupCreated:
case .standardOutgoing, .standardIncoming, ._legacyStandardIncomingDeleted,
.standardIncomingDeleted, .standardIncomingDeletedLocally, .standardOutgoingDeleted,
.standardOutgoingDeletedLocally, .infoLegacyGroupCreated:
return nil
case .infoLegacyGroupUpdated, .infoLegacyGroupCurrentUserLeft: self.variant = .legacyGroupControlMessage

@ -73,7 +73,9 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
}
public enum Variant: Int, Codable, Hashable, DatabaseValueConvertible, CaseIterable {
case standardIncoming
case _legacyStandardIncomingDeleted = 2 // Had an incorrect index so broke this...
case standardIncoming = 0
case standardOutgoing
// Deleted message variants
@ -988,7 +990,7 @@ public extension Interaction {
using dependencies: Dependencies
) -> String {
switch variant {
case .standardIncomingDeleted, .standardIncomingDeletedLocally,
case ._legacyStandardIncomingDeleted, .standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally:
return ""
@ -1105,7 +1107,8 @@ public extension Interaction.Variant {
.infoGroupMembersUpdated:
return true
case .standardIncoming, .standardOutgoing, .standardIncomingDeleted, .standardIncomingDeletedLocally,
case .standardIncoming, .standardOutgoing, ._legacyStandardIncomingDeleted,
.standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally:
return false
}
@ -1151,8 +1154,9 @@ public extension Interaction.Variant {
.infoGroupMembersUpdated:
return true
case .standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally:
case ._legacyStandardIncomingDeleted, .standardIncomingDeleted,
.standardIncomingDeletedLocally, .standardOutgoingDeleted,
.standardOutgoingDeletedLocally:
return false
}
}
@ -1162,7 +1166,8 @@ public extension Interaction.Variant {
case .standardIncoming: return .sent
case .standardOutgoing: return .sending
case .standardIncomingDeleted, .standardIncomingDeletedLocally, .standardOutgoingDeleted,
case ._legacyStandardIncomingDeleted, .standardIncomingDeleted,
.standardIncomingDeletedLocally, .standardOutgoingDeleted,
.standardOutgoingDeletedLocally:
return .deleted
@ -1189,8 +1194,8 @@ public extension Interaction.Variant {
/// after being read (if we don't do this their expiration timer will start immediately when received)
return true
case .standardOutgoing, .standardIncomingDeleted, .standardIncomingDeletedLocally,
.standardOutgoingDeleted, .standardOutgoingDeletedLocally:
case .standardOutgoing, ._legacyStandardIncomingDeleted, .standardIncomingDeleted,
.standardIncomingDeletedLocally, .standardOutgoingDeleted, .standardOutgoingDeletedLocally:
return false
case .infoLegacyGroupCreated, .infoLegacyGroupUpdated, .infoLegacyGroupCurrentUserLeft,

Loading…
Cancel
Save