Fixed the broken tests and resolved some warnings

Stopped migration logs from appearing in unit tests

# Conflicts:
#	Session/Settings/RemoveUsersModal.swift
#	SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift
#	SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift
#	SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift
pull/856/head
Morgan Pretty 2 years ago
parent ca4ce52402
commit 70ff2b49f0

@ -7462,6 +7462,7 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100";
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionTests; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -7570,6 +7571,7 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100";
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionUtilitiesKitTests; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionUtilitiesKitTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -7676,6 +7678,7 @@
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS $(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER -D SQLITE_ENABLE_FTS5 -Xfrontend -warn-long-expression-type-checking=100";
PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionMessagingKitTests; PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionMessagingKitTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;

@ -177,9 +177,6 @@ class EmojiPickerSheet: BaseVC {
let curveValue: Int = ((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIView.AnimationOptions.curveEaseInOut.rawValue)) let curveValue: Int = ((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIView.AnimationOptions.curveEaseInOut.rawValue))
let options: UIView.AnimationOptions = UIView.AnimationOptions(rawValue: UInt(curveValue << 16)) let options: UIView.AnimationOptions = UIView.AnimationOptions(rawValue: UInt(curveValue << 16))
let keyboardRect: CGRect = ((userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? CGRect.zero)
let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY)
UIView.animate( UIView.animate(
withDuration: duration, withDuration: duration,
delay: 0, delay: 0,

@ -84,6 +84,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
.map { isEditing in (isEditing ? .editing : .standard) } .map { isEditing in (isEditing ? .editing : .standard) }
.removeDuplicates() .removeDuplicates()
.prepend(.standard) // Initial value .prepend(.standard) // Initial value
.shareReplay(1)
.eraseToAnyPublisher() .eraseToAnyPublisher()
}() }()

@ -218,7 +218,7 @@ class GifPickerCell: UICollectionViewCell {
self.themeBackgroundColor = nil self.themeBackgroundColor = nil
if self.isCellSelected { if self.isCellSelected {
let activityIndicator = UIActivityIndicatorView(style: .gray) let activityIndicator = UIActivityIndicatorView(style: .medium)
self.activityIndicator = activityIndicator self.activityIndicator = activityIndicator
addSubview(activityIndicator) addSubview(activityIndicator)
activityIndicator.autoCenterInSuperview() activityIndicator.autoCenterInSuperview()

@ -215,8 +215,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
buttonItem.tapPublisher buttonItem.tapPublisher
.map { _ in item.id } .map { _ in item.id }
.handleEvents(receiveOutput: { _ in item.action?() }) .sink(receiveValue: { _ in item.action?() })
.sink(into: self?.viewModel.navItemTapped)
.store(in: &buttonItem.disposables) .store(in: &buttonItem.disposables)
return buttonItem return buttonItem
@ -238,8 +237,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
buttonItem.tapPublisher buttonItem.tapPublisher
.map { _ in item.id } .map { _ in item.id }
.handleEvents(receiveOutput: { _ in item.action?() }) .sink(receiveValue: { _ in item.action?() })
.sink(into: self?.viewModel.navItemTapped)
.store(in: &buttonItem.disposables) .store(in: &buttonItem.disposables)
return buttonItem return buttonItem

@ -14,7 +14,6 @@ class SessionTableViewModel<NavItemId: Equatable, Section: SessionTableSection,
// MARK: - Input // MARK: - Input
let navItemTapped: PassthroughSubject<NavItemId, Never> = PassthroughSubject()
private let _isEditing: CurrentValueSubject<Bool, Never> = CurrentValueSubject(false) private let _isEditing: CurrentValueSubject<Bool, Never> = CurrentValueSubject(false)
lazy var isEditing: AnyPublisher<Bool, Never> = _isEditing lazy var isEditing: AnyPublisher<Bool, Never> = _isEditing
.removeDuplicates() .removeDuplicates()

@ -496,7 +496,7 @@ public enum SMKLegacy {
let members: [Data] = self.members, let members: [Data] = self.members,
let admins: [Data] = self.admins let admins: [Data] = self.admins
else { else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -514,7 +514,7 @@ public enum SMKLegacy {
case "encryptionKeyPair": case "encryptionKeyPair":
guard let wrappers: [_KeyPairWrapper] = self.wrappers else { guard let wrappers: [_KeyPairWrapper] = self.wrappers else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -525,7 +525,7 @@ public enum SMKLegacy {
let publicKey: String = wrapper.publicKey, let publicKey: String = wrapper.publicKey,
let encryptedKeyPair: Data = wrapper.encryptedKeyPair let encryptedKeyPair: Data = wrapper.encryptedKeyPair
else { else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -538,7 +538,7 @@ public enum SMKLegacy {
case "nameChange": case "nameChange":
guard let name: String = self.name else { guard let name: String = self.name else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -548,7 +548,7 @@ public enum SMKLegacy {
case "membersAdded": case "membersAdded":
guard let members: [Data] = self.members else { guard let members: [Data] = self.members else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -556,7 +556,7 @@ public enum SMKLegacy {
case "membersRemoved": case "membersRemoved":
guard let members: [Data] = self.members else { guard let members: [Data] = self.members else {
SNLog("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage") SNLogNotTests("[Migration Error] Unable to decode Legacy ClosedGroupControlMessage")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -602,7 +602,7 @@ public enum SMKLegacy {
case "screenshot": return .screenshot case "screenshot": return .screenshot
case "mediaSaved": case "mediaSaved":
guard let timestamp: UInt64 = self.timestamp else { guard let timestamp: UInt64 = self.timestamp else {
SNLog("[Migration Error] Unable to decode Legacy DataExtractionNotification") SNLogNotTests("[Migration Error] Unable to decode Legacy DataExtractionNotification")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -1646,7 +1646,7 @@ public enum SMKLegacy {
} }
else if _MessageSendJob.process(rawDestination, type: "openGroup") != nil { else if _MessageSendJob.process(rawDestination, type: "openGroup") != nil {
// We can no longer support sending messages to legacy open groups // We can no longer support sending messages to legacy open groups
SNLog("[Migration Warning] Ignoring pending messageSend job for V1 OpenGroup") SNLogNotTests("[Migration Warning] Ignoring pending messageSend job for V1 OpenGroup")
return nil return nil
} }
else if let destString: String = _MessageSendJob.process(rawDestination, type: "openGroupV2") { else if let destString: String = _MessageSendJob.process(rawDestination, type: "openGroupV2") {

@ -21,7 +21,7 @@ enum _003_YDBToGRDBMigration: Migration {
// We want this setting to be on by default (even if there isn't a legacy database) // We want this setting to be on by default (even if there isn't a legacy database)
db[.trimOpenGroupMessagesOlderThanSixMonths] = true db[.trimOpenGroupMessagesOlderThanSixMonths] = true
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return return
} }
@ -76,7 +76,7 @@ enum _003_YDBToGRDBMigration: Migration {
// same changes if the migration hasn't already run) // same changes if the migration hasn't already run)
transaction.enumerateKeys(inCollection: SMKLegacy.databaseMigrationCollection) { key, _ in transaction.enumerateKeys(inCollection: SMKLegacy.databaseMigrationCollection) { key, _ in
guard let legacyMigration: SMKLegacy._DBMigration = SMKLegacy._DBMigration(rawValue: key) else { guard let legacyMigration: SMKLegacy._DBMigration = SMKLegacy._DBMigration(rawValue: key) else {
SNLog("[Migration Error] Found unknown migration") SNLogNotTests("[Migration Error] Found unknown migration")
shouldFailMigration = true shouldFailMigration = true
return return
} }
@ -87,7 +87,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Contacts // MARK: --Contacts
SNLog("[Migration Info] \(target.key(with: self)) - Processing Contacts") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Contacts")
transaction.enumerateRows(inCollection: SMKLegacy.contactCollection) { _, object, _, _ in transaction.enumerateRows(inCollection: SMKLegacy.contactCollection) { _, object, _, _ in
guard let contact = object as? SMKLegacy._Contact else { return } guard let contact = object as? SMKLegacy._Contact else { return }
@ -106,7 +106,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Threads // MARK: --Threads
SNLog("[Migration Info] \(target.key(with: self)) - Processing Threads") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Threads")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { key, object, _ in transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.threadCollection) { key, object, _ in
guard let thread: SMKLegacy._Thread = object as? SMKLegacy._Thread else { return } guard let thread: SMKLegacy._Thread = object as? SMKLegacy._Thread else { return }
@ -143,7 +143,7 @@ enum _003_YDBToGRDBMigration: Migration {
let groupId: String = String(data: groupIdData, encoding: .utf8), let groupId: String = String(data: groupIdData, encoding: .utf8),
let publicKey: String = groupId.split(separator: "!").last.map({ String($0) }) let publicKey: String = groupId.split(separator: "!").last.map({ String($0) })
else { else {
SNLog("[Migration Error] Unable to decode Closed Group") SNLogNotTests("[Migration Error] Unable to decode Closed Group")
shouldFailMigration = true shouldFailMigration = true
return return
} }
@ -173,7 +173,7 @@ enum _003_YDBToGRDBMigration: Migration {
} }
else if groupThread.isOpenGroup { else if groupThread.isOpenGroup {
guard let openGroup: SMKLegacy._OpenGroup = transaction.object(forKey: thread.uniqueId, inCollection: SMKLegacy.openGroupCollection) as? SMKLegacy._OpenGroup else { guard let openGroup: SMKLegacy._OpenGroup = transaction.object(forKey: thread.uniqueId, inCollection: SMKLegacy.openGroupCollection) as? SMKLegacy._OpenGroup else {
SNLog("[Migration Error] Unable to find open group info") SNLogNotTests("[Migration Error] Unable to find open group info")
shouldFailMigration = true shouldFailMigration = true
return return
} }
@ -204,7 +204,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Interactions // MARK: --Interactions
SNLog("[Migration Info] \(target.key(with: self)) - Processing Interactions") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Interactions")
/// **Note:** There is no index on the collection column so unfortunately it takes the same amount of time to enumerate through all /// **Note:** There is no index on the collection column so unfortunately it takes the same amount of time to enumerate through all
/// collections as it does to just get the count of collections, due to this, if the database is very large, importing thecollections can be /// collections as it does to just get the count of collections, due to this, if the database is very large, importing thecollections can be
@ -222,7 +222,7 @@ enum _003_YDBToGRDBMigration: Migration {
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.interactionCollection) { _, object, _ in transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.interactionCollection) { _, object, _ in
guard let interaction: SMKLegacy._DBInteraction = object as? SMKLegacy._DBInteraction else { guard let interaction: SMKLegacy._DBInteraction = object as? SMKLegacy._DBInteraction else {
SNLog("[Migration Error] Unable to process interaction") SNLogNotTests("[Migration Error] Unable to process interaction")
shouldFailMigration = true shouldFailMigration = true
return return
} }
@ -262,11 +262,11 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Attachments // MARK: --Attachments
SNLog("[Migration Info] \(target.key(with: self)) - Processing Attachments") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Attachments")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.attachmentsCollection) { key, object, _ in transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.attachmentsCollection) { key, object, _ in
guard let attachment: SMKLegacy._Attachment = object as? SMKLegacy._Attachment else { guard let attachment: SMKLegacy._Attachment = object as? SMKLegacy._Attachment else {
SNLog("[Migration Error] Unable to process attachment") SNLogNotTests("[Migration Error] Unable to process attachment")
shouldFailMigration = true shouldFailMigration = true
return return
} }
@ -306,7 +306,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Jobs // MARK: --Jobs
SNLog("[Migration Info] \(target.key(with: self)) - Processing Jobs") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Jobs")
transaction.enumerateRows(inCollection: SMKLegacy.notifyPushServerJobCollection) { _, object, _, _ in transaction.enumerateRows(inCollection: SMKLegacy.notifyPushServerJobCollection) { _, object, _, _ in
guard let job = object as? SMKLegacy._NotifyPNServerJob else { return } guard let job = object as? SMKLegacy._NotifyPNServerJob else { return }
@ -336,7 +336,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: --Preferences // MARK: --Preferences
SNLog("[Migration Info] \(target.key(with: self)) - Processing Preferences") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Processing Preferences")
transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.preferencesCollection) { key, object, _ in transaction.enumerateKeysAndObjects(inCollection: SMKLegacy.preferencesCollection) { key, object, _ in
legacyPreferences[key] = object legacyPreferences[key] = object
@ -403,7 +403,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: - Insert Contacts // MARK: - Insert Contacts
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Contacts") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Contacts")
try autoreleasepool { try autoreleasepool {
// Values for contact progress // Values for contact progress
@ -509,7 +509,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: - Insert Threads // MARK: - Insert Threads
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Threads & Interactions") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Threads & Interactions")
var legacyInteractionToIdMap: [String: Int64] = [:] var legacyInteractionToIdMap: [String: Int64] = [:]
var legacyInteractionIdentifierToIdMap: [String: Int64] = [:] var legacyInteractionIdentifierToIdMap: [String: Int64] = [:]
@ -557,7 +557,7 @@ enum _003_YDBToGRDBMigration: Migration {
// Sort by id just so we can make the migration process more determinstic // Sort by id just so we can make the migration process more determinstic
try legacyThreads.sorted(by: { lhs, rhs in lhs.uniqueId < rhs.uniqueId }).forEach { legacyThread in try legacyThreads.sorted(by: { lhs, rhs in lhs.uniqueId < rhs.uniqueId }).forEach { legacyThread in
guard let threadId: String = legacyThreadIdToIdMap[legacyThread.uniqueId] else { guard let threadId: String = legacyThreadIdToIdMap[legacyThread.uniqueId] else {
SNLog("[Migration Error] Unable to migrate thread with no id mapping") SNLogNotTests("[Migration Error] Unable to migrate thread with no id mapping")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -610,7 +610,7 @@ enum _003_YDBToGRDBMigration: Migration {
let groupModel: SMKLegacy._GroupModel = closedGroupModel[legacyThread.uniqueId], let groupModel: SMKLegacy._GroupModel = closedGroupModel[legacyThread.uniqueId],
let formationTimestamp: UInt64 = closedGroupFormation[legacyThread.uniqueId] let formationTimestamp: UInt64 = closedGroupFormation[legacyThread.uniqueId]
else { else {
SNLog("[Migration Error] Closed group missing required data") SNLogNotTests("[Migration Error] Closed group missing required data")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -635,7 +635,7 @@ enum _003_YDBToGRDBMigration: Migration {
// Create the 'GroupMember' models for the group (even if the current user is no longer // Create the 'GroupMember' models for the group (even if the current user is no longer
// a member as these objects are used to generate the group avatar icon) // a member as these objects are used to generate the group avatar icon)
func createDummyProfile(profileId: String) { func createDummyProfile(profileId: String) {
SNLog("[Migration Warning] Closed group member with unknown user found - Creating empty profile") SNLogNotTests("[Migration Warning] Closed group member with unknown user found - Creating empty profile")
// Note: Need to upsert here because it's possible multiple quotes // Note: Need to upsert here because it's possible multiple quotes
// will use the same invalid 'authorId' value resulting in a unique // will use the same invalid 'authorId' value resulting in a unique
@ -692,7 +692,7 @@ enum _003_YDBToGRDBMigration: Migration {
let openGroup: SMKLegacy._OpenGroup = openGroupInfo[legacyThread.uniqueId], let openGroup: SMKLegacy._OpenGroup = openGroupInfo[legacyThread.uniqueId],
let targetOpenGroupServer: String = openGroupServer[legacyThread.uniqueId] let targetOpenGroupServer: String = openGroupServer[legacyThread.uniqueId]
else { else {
SNLog("[Migration Error] Open group missing required data") SNLogNotTests("[Migration Error] Open group missing required data")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -877,7 +877,7 @@ enum _003_YDBToGRDBMigration: Migration {
} }
default: default:
SNLog("[Migration Error] Unsupported interaction type") SNLogNotTests("[Migration Error] Unsupported interaction type")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -940,11 +940,11 @@ enum _003_YDBToGRDBMigration: Migration {
switch error { switch error {
// Ignore duplicate interactions // Ignore duplicate interactions
case DatabaseError.SQLITE_CONSTRAINT_UNIQUE: case DatabaseError.SQLITE_CONSTRAINT_UNIQUE:
SNLog("[Migration Warning] Found duplicate message of variant: \(variant); skipping") SNLogNotTests("[Migration Warning] Found duplicate message of variant: \(variant); skipping")
return return
default: default:
SNLog("[Migration Error] Failed to insert interaction") SNLogNotTests("[Migration Error] Failed to insert interaction")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
} }
@ -961,7 +961,7 @@ enum _003_YDBToGRDBMigration: Migration {
receivedMessageTimestamps.remove(legacyInteraction.timestamp) receivedMessageTimestamps.remove(legacyInteraction.timestamp)
guard let interactionId: Int64 = interaction.id else { guard let interactionId: Int64 = interaction.id else {
SNLog("[Migration Error] Failed to insert interaction") SNLogNotTests("[Migration Error] Failed to insert interaction")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -1047,7 +1047,7 @@ enum _003_YDBToGRDBMigration: Migration {
// an older message not cached to the device) - this will cause a foreign // an older message not cached to the device) - this will cause a foreign
// key constraint violation so in these cases just create an empty profile // key constraint violation so in these cases just create an empty profile
if !validProfileIds.contains(quotedMessage.authorId) { if !validProfileIds.contains(quotedMessage.authorId) {
SNLog("[Migration Warning] Quote with unknown author found - Creating empty profile") SNLogNotTests("[Migration Warning] Quote with unknown author found - Creating empty profile")
// Note: Need to upsert here because it's possible multiple quotes // Note: Need to upsert here because it's possible multiple quotes
// will use the same invalid 'authorId' value resulting in a unique // will use the same invalid 'authorId' value resulting in a unique
@ -1078,7 +1078,7 @@ enum _003_YDBToGRDBMigration: Migration {
.attachmentIds .attachmentIds
.first .first
SNLog([ SNLogNotTests([
"[Migration Warning] Quote with invalid attachmentId found", "[Migration Warning] Quote with invalid attachmentId found",
(quoteAttachmentId == nil ? (quoteAttachmentId == nil ?
"Unable to reconcile, leaving attachment blank" : "Unable to reconcile, leaving attachment blank" :
@ -1157,7 +1157,7 @@ enum _003_YDBToGRDBMigration: Migration {
) )
guard let attachmentId: String = maybeAttachmentId else { guard let attachmentId: String = maybeAttachmentId else {
SNLog("[Migration Warning] Failed to create invalid attachment for missing attachment") SNLogNotTests("[Migration Warning] Failed to create invalid attachment for missing attachment")
return return
} }
@ -1214,7 +1214,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: - Insert Jobs // MARK: - Insert Jobs
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Jobs") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Jobs")
// MARK: --notifyPushServer // MARK: --notifyPushServer
@ -1333,7 +1333,7 @@ enum _003_YDBToGRDBMigration: Migration {
switch legacyJob.message { switch legacyJob.message {
case is SMKLegacy._VisibleMessage: case is SMKLegacy._VisibleMessage:
guard interactionId != nil else { guard interactionId != nil else {
SNLog("[Migration Warning] Unable to find associated interaction to messageSend job, ignoring.") SNLogNotTests("[Migration Warning] Unable to find associated interaction to messageSend job, ignoring.")
return return
} }
@ -1369,7 +1369,7 @@ enum _003_YDBToGRDBMigration: Migration {
try autoreleasepool { try autoreleasepool {
try attachmentUploadJobs.forEach { legacyJob in try attachmentUploadJobs.forEach { legacyJob in
guard let sendJob: Job = messageSendJobLegacyMap[legacyJob.messageSendJobID], let sendJobId: Int64 = sendJob.id else { guard let sendJob: Job = messageSendJobLegacyMap[legacyJob.messageSendJobID], let sendJobId: Int64 = sendJob.id else {
SNLog("[Migration Error] attachmentUpload job missing associated MessageSendJob") SNLogNotTests("[Migration Error] attachmentUpload job missing associated MessageSendJob")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -1387,7 +1387,7 @@ enum _003_YDBToGRDBMigration: Migration {
// Add the dependency to the relevant MessageSendJob // Add the dependency to the relevant MessageSendJob
guard let uploadJobId: Int64 = uploadJob?.id else { guard let uploadJobId: Int64 = uploadJob?.id else {
SNLog("[Migration Error] attachmentUpload job was not created") SNLogNotTests("[Migration Error] attachmentUpload job was not created")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -1404,12 +1404,12 @@ enum _003_YDBToGRDBMigration: Migration {
try attachmentDownloadJobs.forEach { legacyJob in try attachmentDownloadJobs.forEach { legacyJob in
guard let interactionId: Int64 = legacyInteractionToIdMap[legacyJob.tsMessageID] else { guard let interactionId: Int64 = legacyInteractionToIdMap[legacyJob.tsMessageID] else {
// This can happen if an UnsendRequest came before an AttachmentDownloadJob completed // This can happen if an UnsendRequest came before an AttachmentDownloadJob completed
SNLog("[Migration Warning] attachmentDownload job with no interaction found - ignoring") SNLogNotTests("[Migration Warning] attachmentDownload job with no interaction found - ignoring")
return return
} }
guard processedAttachmentIds.contains(legacyJob.attachmentID) else { guard processedAttachmentIds.contains(legacyJob.attachmentID) else {
// Unsure how this case can occur but it seemed to happen when testing internally // Unsure how this case can occur but it seemed to happen when testing internally
SNLog("[Migration Warning] attachmentDownload job unable to find attachment - ignoring") SNLogNotTests("[Migration Warning] attachmentDownload job unable to find attachment - ignoring")
return return
} }
@ -1446,7 +1446,7 @@ enum _003_YDBToGRDBMigration: Migration {
// MARK: - Preferences // MARK: - Preferences
SNLog("[Migration Info] \(target.key(with: self)) - Inserting Preferences") SNLogNotTests("[Migration Info] \(target.key(with: self)) - Inserting Preferences")
db[.defaultNotificationSound] = Preferences.Sound(rawValue: legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] as? Int ?? -1) db[.defaultNotificationSound] = Preferences.Sound(rawValue: legacyPreferences[SMKLegacy.soundsGlobalNotificationKey] as? Int ?? -1)
.defaulting(to: Preferences.Sound.defaultNotificationSound) .defaulting(to: Preferences.Sound.defaultNotificationSound)
@ -1502,7 +1502,7 @@ enum _003_YDBToGRDBMigration: Migration {
guard let legacyAttachmentId: String = legacyAttachmentId else { return nil } guard let legacyAttachmentId: String = legacyAttachmentId else { return nil }
guard !processedAttachmentIds.contains(legacyAttachmentId) else { guard !processedAttachmentIds.contains(legacyAttachmentId) else {
guard isQuotedMessage else { guard isQuotedMessage else {
SNLog("[Migration Error] Attempted to process duplicate attachment") SNLogNotTests("[Migration Error] Attempted to process duplicate attachment")
throw StorageError.migrationFailed throw StorageError.migrationFailed
} }
@ -1510,7 +1510,7 @@ enum _003_YDBToGRDBMigration: Migration {
} }
guard let legacyAttachment: SMKLegacy._Attachment = attachments[legacyAttachmentId] else { guard let legacyAttachment: SMKLegacy._Attachment = attachments[legacyAttachmentId] else {
SNLog("[Migration Warning] Missing attachment - interaction will show a \"failed\" attachment") SNLogNotTests("[Migration Warning] Missing attachment - interaction will show a \"failed\" attachment")
return nil return nil
} }

@ -509,6 +509,10 @@ public final class OpenGroupManager {
on: server, on: server,
using: dependencies using: dependencies
) )
// Note: We need to subscribe and receive on different threads to ensure the
// logic in 'receiveValue' doesn't result in a reentrancy database issue
.subscribe(on: OpenGroupAPI.workQueue)
.receive(on: DispatchQueue.global(qos: .default))
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { _ in receiveCompletion: { _ in
if waitForImageToComplete { if waitForImageToComplete {

@ -865,7 +865,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -937,7 +937,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1031,7 +1031,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1089,7 +1089,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1163,7 +1163,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1253,7 +1253,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1319,7 +1319,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1366,7 +1366,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1408,7 +1408,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1445,7 +1445,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1480,7 +1480,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1522,7 +1522,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1564,7 +1564,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1601,7 +1601,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1644,7 +1644,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1696,7 +1696,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1752,7 +1752,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1795,7 +1795,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1836,7 +1836,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1872,7 +1872,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1906,7 +1906,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1947,7 +1947,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -1988,7 +1988,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2024,7 +2024,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2066,7 +2066,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2103,7 +2103,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2149,7 +2149,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2191,7 +2191,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2231,7 +2231,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2270,7 +2270,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2312,7 +2312,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2350,7 +2350,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2389,7 +2389,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2428,7 +2428,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2487,7 +2487,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2539,7 +2539,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2571,7 +2571,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2605,7 +2605,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2653,7 +2653,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2684,7 +2684,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2717,7 +2717,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2768,7 +2768,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2802,7 +2802,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2838,7 +2838,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2874,7 +2874,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2937,7 +2937,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -2968,7 +2968,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3025,7 +3025,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3054,7 +3054,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3083,7 +3083,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3116,7 +3116,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3154,7 +3154,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3189,7 +3189,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3228,7 +3228,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()
@ -3257,7 +3257,7 @@ class OpenGroupAPISpec: QuickSpec {
} }
.subscribe(on: DispatchQueue.main) .subscribe(on: DispatchQueue.main)
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.handleEvents(receiveOutput: { result in pollResponse = result }) .handleEvents(receiveOutput: { result in response = result })
.mapError { error.setting(to: $0) } .mapError { error.setting(to: $0) }
.sinkUntilComplete() .sinkUntilComplete()

@ -17,7 +17,7 @@ enum _003_YDBToGRDBMigration: Migration {
static func migrate(_ db: Database) throws { static func migrate(_ db: Database) throws {
guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else { guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else {
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return return
} }

@ -86,6 +86,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
id: ThreadDisappearingMessagesViewModel.Item( id: ThreadDisappearingMessagesViewModel.Item(
title: "DISAPPEARING_MESSAGES_OFF".localized() title: "DISAPPEARING_MESSAGES_OFF".localized()
), ),
position: .top,
title: "DISAPPEARING_MESSAGES_OFF".localized(), title: "DISAPPEARING_MESSAGES_OFF".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { true } isSelected: { true }
@ -101,7 +102,8 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
.to( .to(
equal( equal(
SessionCell.Info( SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(title: title), id: ThreadDisappearingMessagesSettingsViewModel.Item(title: title),
position: .bottom,
title: title, title: title,
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }
@ -142,6 +144,7 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
id: ThreadDisappearingMessagesViewModel.Item( id: ThreadDisappearingMessagesViewModel.Item(
title: "DISAPPEARING_MESSAGES_OFF".localized() title: "DISAPPEARING_MESSAGES_OFF".localized()
), ),
position: .top,
title: "DISAPPEARING_MESSAGES_OFF".localized(), title: "DISAPPEARING_MESSAGES_OFF".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }
@ -157,7 +160,8 @@ class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
.to( .to(
equal( equal(
SessionCell.Info( SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(title: title), id: ThreadDisappearingMessagesSettingsViewModel.Item(title: title),
position: .bottom,
title: title, title: title,
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { true } isSelected: { true }

@ -15,7 +15,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
override func spec() { override func spec() {
var mockStorage: Storage! var mockStorage: Storage!
var mockGeneralCache: MockGeneralCache! var mockGeneralCache: MockGeneralCache!
var cancellables: [AnyCancellable] = [] var disposables: [AnyCancellable] = []
var dependencies: Dependencies! var dependencies: Dependencies!
var viewModel: ThreadSettingsViewModel! var viewModel: ThreadSettingsViewModel!
var didTriggerSearchCallbackTriggered: Bool = false var didTriggerSearchCallbackTriggered: Bool = false
@ -69,21 +69,33 @@ class ThreadSettingsViewModelSpec: QuickSpec {
didTriggerSearchCallbackTriggered = true didTriggerSearchCallbackTriggered = true
} }
) )
cancellables.append( setupStandardBinding()
viewModel.observableSettingsData }
func setupStandardBinding() {
disposables.append(
viewModel.observableTableData
.receiveOnMain(immediately: true) .receiveOnMain(immediately: true)
.sink( .sink(
receiveCompletion: { _ in }, receiveCompletion: { _ in },
receiveValue: { viewModel.updateSettings($0) } receiveValue: { viewModel.updateTableData($0.0) }
)
)
disposables.append(
viewModel.transitionToScreen
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { transitionInfo = $0 }
) )
) )
} }
afterEach { afterEach {
cancellables.forEach { $0.cancel() } disposables.forEach { $0.cancel() }
mockStorage = nil mockStorage = nil
cancellables = [] disposables = []
dependencies = nil dependencies = nil
viewModel = nil viewModel = nil
didTriggerSearchCallbackTriggered = false didTriggerSearchCallbackTriggered = false
@ -207,6 +219,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
context("when entering edit mode") { context("when entering edit mode") {
beforeEach { beforeEach {
viewModel.navState.sinkAndStore(in: &disposables)
viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.rightNavItems.firstValue()??.first?.action?()
viewModel.textChanged("TestNew", for: .nickname) viewModel.textChanged("TestNew", for: .nickname)
} }
@ -332,8 +345,9 @@ class ThreadSettingsViewModelSpec: QuickSpec {
context("when entering edit mode") { context("when entering edit mode") {
beforeEach { beforeEach {
viewModel.navState.sinkAndStore(in: &disposables)
viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.rightNavItems.firstValue()??.first?.action?()
viewModel.textChanged("TestNew", for: .nickname) viewModel.textChanged("TestUserNew", for: .nickname)
} }
it("enters the editing state") { it("enters the editing state") {

@ -67,6 +67,7 @@ class NotificationContentViewModelSpec: QuickSpec {
equal([ equal([
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.nameAndPreview, id: Preferences.NotificationPreviewType.nameAndPreview,
position: .top,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { true } isSelected: { true }
@ -74,6 +75,7 @@ class NotificationContentViewModelSpec: QuickSpec {
), ),
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.nameNoPreview, id: Preferences.NotificationPreviewType.nameNoPreview,
position: .middle,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }
@ -81,6 +83,7 @@ class NotificationContentViewModelSpec: QuickSpec {
), ),
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.noNameNoPreview, id: Preferences.NotificationPreviewType.noNameNoPreview,
position: .bottom,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }
@ -107,6 +110,7 @@ class NotificationContentViewModelSpec: QuickSpec {
equal([ equal([
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.nameAndPreview, id: Preferences.NotificationPreviewType.nameAndPreview,
position: .top,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }
@ -114,6 +118,7 @@ class NotificationContentViewModelSpec: QuickSpec {
), ),
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.nameNoPreview, id: Preferences.NotificationPreviewType.nameNoPreview,
position: .middle,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { true } isSelected: { true }
@ -121,6 +126,7 @@ class NotificationContentViewModelSpec: QuickSpec {
), ),
SessionCell.Info( SessionCell.Info(
id: Preferences.NotificationPreviewType.noNameNoPreview, id: Preferences.NotificationPreviewType.noNameNoPreview,
position: .bottom,
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(), title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(),
rightAccessory: .radio( rightAccessory: .radio(
isSelected: { false } isSelected: { false }

@ -12,7 +12,7 @@ enum _003_YDBToGRDBMigration: Migration {
static func migrate(_ db: Database) throws { static func migrate(_ db: Database) throws {
guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else { guard let dbConnection: YapDatabaseConnection = SUKLegacy.newDatabaseConnection() else {
SNLog("[Migration Warning] No legacy database, skipping \(target.key(with: self))") SNLogNotTests("[Migration Warning] No legacy database, skipping \(target.key(with: self))")
return return
} }

@ -15,9 +15,9 @@ public protocol Migration {
public extension Migration { public extension Migration {
static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) { static func loggedMigrate(_ targetIdentifier: TargetMigrations.Identifier) -> ((_ db: Database) throws -> ()) {
return { (db: Database) in return { (db: Database) in
SNLog("[Migration Info] Starting \(targetIdentifier.key(with: self))") SNLogNotTests("[Migration Info] Starting \(targetIdentifier.key(with: self))")
try migrate(db) try migrate(db)
SNLog("[Migration Info] Completed \(targetIdentifier.key(with: self))") SNLogNotTests("[Migration Info] Completed \(targetIdentifier.key(with: self))")
} }
} }
} }

@ -1,3 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SignalCoreKit import SignalCoreKit
public func SNLog(_ message: String) { public func SNLog(_ message: String) {
@ -6,3 +9,9 @@ public func SNLog(_ message: String) {
#endif #endif
OWSLogger.info("[Session] \(message)") OWSLogger.info("[Session] \(message)")
} }
public func SNLogNotTests(_ message: String) {
guard ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil else { return }
SNLog(message)
}

@ -274,7 +274,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
it("succeeds when using the migration safe mutable upsert and the item does not already exist") { it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
mockStorage.write { db in mockStorage.write { db in
expect { expect {
var result = MutableTestType(columnA: "Test14", columnB: "Test14B") let result = MutableTestType(columnA: "Test14", columnB: "Test14B")
try result.migrationSafeUpsert(db) try result.migrationSafeUpsert(db)
return result return result
} }
@ -620,7 +620,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
it("succeeds when using the migration safe mutable upsert and the item does not already exist") { it("succeeds when using the migration safe mutable upsert and the item does not already exist") {
mockStorage.write { db in mockStorage.write { db in
expect { expect {
var result = MutableTestType(columnA: "Test21", columnB: "Test21B") let result = MutableTestType(columnA: "Test21", columnB: "Test21B")
try result.migrationSafeUpsert(db) try result.migrationSafeUpsert(db)
return result return result
} }
@ -650,7 +650,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)", sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test23"]) arguments: StatementArguments(["Test23"])
) )
var result = MutableTestType(id: 1, columnA: "Test23", columnB: "Test23B") let result = MutableTestType(id: 1, columnA: "Test23", columnB: "Test23B")
try result.migrationSafeUpsert(db) try result.migrationSafeUpsert(db)
return result return result
} }
@ -661,7 +661,7 @@ class PersistableRecordUtilitiesSpec: QuickSpec {
sql: "INSERT INTO MutableTestType (columnA) VALUES (?)", sql: "INSERT INTO MutableTestType (columnA) VALUES (?)",
arguments: StatementArguments(["Test24"]) arguments: StatementArguments(["Test24"])
) )
var result = MutableTestType(id: 2, columnA: "Test24", columnB: "Test24B") let result = MutableTestType(id: 2, columnA: "Test24", columnB: "Test24B")
try result.migrationSafeUpsert(db) try result.migrationSafeUpsert(db)
return result.id return result.id
} }

@ -76,11 +76,11 @@ public class DirectionalPanGestureRecognizer: UIPanGestureRecognizer {
let vel = velocity(in: view) let vel = velocity(in: view)
switch direction { switch direction {
case .left, .right: case .left, .right:
if fabs(vel.y) > fabs(vel.x) { if abs(vel.y) > abs(vel.x) {
state = .cancelled state = .cancelled
} }
case .up, .down: case .up, .down:
if fabs(vel.x) > fabs(vel.y) { if abs(vel.x) > abs(vel.y) {
state = .cancelled state = .cancelled
} }
default: default:

@ -2,8 +2,19 @@
import Foundation import Foundation
import Combine import Combine
import SessionUtilitiesKit
public extension AnyPublisher { public extension AnyPublisher {
func sinkAndStore<C>(in storage: inout C) where C : RangeReplaceableCollection, C.Element == AnyCancellable {
self
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in }
)
.store(in: &storage)
}
func firstValue() -> Output? { func firstValue() -> Output? {
var value: Output? var value: Output?

Loading…
Cancel
Save