From 71bafcc8f06adf1e1741f97d16818fb7257b5282 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 28 Nov 2017 16:23:54 -0800 Subject: [PATCH] Search SignalAccounts by profile name ...and fixup some tests --- Signal.xcodeproj/project.pbxproj | 4 -- .../src/ViewControllers/ContactsViewHelper.m | 54 ++++++++----------- Signal/src/util/Searcher.swift | 11 +++- Signal/test/Models/AccountManagerTest.swift | 1 + .../MesssagesBubblesSizeCalculatorTest.swift | 8 ++- .../ConversationViewItemTest.m | 12 ++++- Signal/test/call/CallAudioSessionTest.swift | 35 ------------ Signal/test/util/SearcherTest.swift | 25 +++++++-- 8 files changed, 71 insertions(+), 79 deletions(-) delete mode 100644 Signal/test/call/CallAudioSessionTest.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d79e7e515..69bb72832 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -279,7 +279,6 @@ 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; }; 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; 45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; - 45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */; }; 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; }; 45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; }; 45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */; }; @@ -866,7 +865,6 @@ 45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; - 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSessionTest.swift; sourceTree = ""; }; 45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = ""; }; 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = ""; }; 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = ""; }; @@ -1837,7 +1835,6 @@ B660F6731C29867F00687D6E /* call */ = { isa = PBXGroup; children = ( - 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */, 456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */, ); path = call; @@ -2928,7 +2925,6 @@ 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */, 452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */, 451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */, - 45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */, 456F6E231E24133500FD2210 /* Platform.swift in Sources */, 4539B5871F79348F007141FF /* PushRegistrationManager.swift in Sources */, 4504493A1F45EE7D002D1ADA /* NSString+OWS.m in Sources */, diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index 1a3f1b3fd..c30d33992 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts; @property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce; @property (nonatomic) OWSProfileManager *profileManager; +@property (nonatomic, readonly) AnySearcher *signalAccountSearcher; @end @@ -59,6 +60,8 @@ NS_ASSUME_NONNULL_BEGIN [self updateContacts]; self.shouldNotifyDelegateOfUpdatedContacts = NO; + _signalAccountSearcher = [self buildAccountSearcher]; + [self observeNotifications]; return self; @@ -99,6 +102,24 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Contacts +- (AnySearcher *)buildAccountSearcher +{ + return [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { + if (![obj isKindOfClass:[SignalAccount class]]) { + OWSFail(@"unexpected item in searcher"); + return @""; + } + + SignalAccount *signalAccount = (SignalAccount *)obj; + + NSString *recipientId = signalAccount.recipientId; + NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:recipientId]; + NSString *profileName = [self.contactsManager profileNameForRecipientId:recipientId]; + + return [NSString stringWithFormat:@"%@ %@ %@", recipientId, contactName, profileName]; + }]; +} + - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId { OWSAssert([NSThread isMainThread]); @@ -173,37 +194,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerm:(NSString *)searchTerm -{ - OWSAssert(signalAccount); - OWSAssert(searchTerm.length > 0); - - if ([signalAccount.contact.fullName.lowercaseString containsString:searchTerm.lowercaseString]) { - return YES; - } - - NSString *asPhoneNumber = [PhoneNumber removeFormattingCharacters:searchTerm]; - if (asPhoneNumber.length > 0 && [signalAccount.recipientId containsString:asPhoneNumber]) { - return YES; - } - - return NO; -} - -- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerms:(NSArray *)searchTerms -{ - OWSAssert(signalAccount); - OWSAssert(searchTerms.count > 0); - - for (NSString *searchTerm in searchTerms) { - if (![self doesSignalAccount:signalAccount matchSearchTerm:searchTerm]) { - return NO; - } - } - - return YES; -} - - (NSArray *)searchTermsForSearchString:(NSString *)searchText { return [[[searchText ows_stripped] @@ -225,7 +215,7 @@ NS_ASSUME_NONNULL_BEGIN return [self.signalAccounts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount, NSDictionary *_Nullable bindings) { - return [self doesSignalAccount:signalAccount matchSearchTerms:searchTerms]; + return [self.signalAccountSearcher item:signalAccount doesMatchQuery:searchText]; }]]; } diff --git a/Signal/src/util/Searcher.swift b/Signal/src/util/Searcher.swift index 0cd260025..aab5fa7fb 100644 --- a/Signal/src/util/Searcher.swift +++ b/Signal/src/util/Searcher.swift @@ -36,7 +36,16 @@ class Searcher { } private func stem(string: String) -> [String] { - return normalize(string: string).components(separatedBy: .whitespaces) + var normalized = normalize(string: string) + + // Remove any phone number formatting from the search terms + let nonformattingScalars = normalized.unicodeScalars.lazy.filter { + !CharacterSet.punctuationCharacters.contains($0) + } + + normalized = String(String.UnicodeScalarView(nonformattingScalars)) + + return normalized.components(separatedBy: .whitespacesAndNewlines) } private func normalize(string: String) -> String { diff --git a/Signal/test/Models/AccountManagerTest.swift b/Signal/test/Models/AccountManagerTest.swift index 3ac8a62d0..7a9589ffb 100644 --- a/Signal/test/Models/AccountManagerTest.swift +++ b/Signal/test/Models/AccountManagerTest.swift @@ -4,6 +4,7 @@ import XCTest import PromiseKit +import SignalServiceKit struct VerificationFailedError: Error { } struct FailedToGetRPRegistrationTokenError: Error { } diff --git a/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift b/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift index f4e96934b..9ee7327ec 100644 --- a/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift +++ b/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift @@ -3,6 +3,7 @@ // import XCTest +import SignalServiceKit /** * This is a brittle test, which will break if our layout changes. @@ -23,7 +24,12 @@ class MesssagesBubblesSizeCalculatorTest: XCTestCase { func viewItemForText(_ text: String?) -> ConversationViewItem { let interaction = TSOutgoingMessage(timestamp: 0, in: thread, messageBody: text) interaction.save() - let viewItem = ConversationViewItem(tsInteraction:interaction, isGroupThread:false) + + var viewItem: ConversationViewItem! + interaction.dbReadWriteConnection().readWrite { transaction in + viewItem = ConversationViewItem(interaction: interaction, isGroupThread: false, transaction: transaction) + } + viewItem.shouldShowDate = false viewItem.shouldHideRecipientStatus = true return viewItem diff --git a/Signal/test/ViewControllers/ConversationViewItemTest.m b/Signal/test/ViewControllers/ConversationViewItemTest.m index 3914015e8..df293aaec 100644 --- a/Signal/test/ViewControllers/ConversationViewItemTest.m +++ b/Signal/test/ViewControllers/ConversationViewItemTest.m @@ -8,6 +8,7 @@ #import #import #import +#import @interface ConversationViewItem (Testing) @@ -64,8 +65,10 @@ OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:filePath]); - TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType sourceFilename:nil]; DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; + TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType + byteCount:(UInt32)dataSource.dataLength + sourceFilename:nil]; BOOL success = [attachment writeDataSource:dataSource]; OWSAssert(success); [attachment save]; @@ -75,7 +78,12 @@ TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 inThread:nil messageBody:nil attachmentIds:attachmentIds]; [message save]; - ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO]; + + __block ConversationViewItem *viewItem = nil; + [TSYapDatabaseObject.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO transaction:transaction]; + }]; + return viewItem; } diff --git a/Signal/test/call/CallAudioSessionTest.swift b/Signal/test/call/CallAudioSessionTest.swift deleted file mode 100644 index 7e3e6d042..000000000 --- a/Signal/test/call/CallAudioSessionTest.swift +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2017 Open Whisper Systems. All rights reserved. -// - -import XCTest -import AVKit -import WebRTC - -/** - * These tests are obtuse - they just assert the exact implementation of the methods. Normally I wouldn't include them, - * but these methods make use of a header not included in the standard distribution of the WebRTC.framework. We've - * included the header in our local project, and test the methods here to make sure that they are still available when - * we upgrade the framework. - * - * If they are failing, it's possible the RTCAudioSession header, and our usage of it, need to be updated. - */ -class CallAudioSessionTest: XCTestCase { - func testAudioSession() { - - let rtcAudioSession = RTCAudioSession.sharedInstance() - // Sanity Check - XCTAssertFalse(rtcAudioSession.useManualAudio) - - CallAudioSession().configure() - XCTAssertTrue(rtcAudioSession.useManualAudio) - XCTAssertFalse(rtcAudioSession.isAudioEnabled) - - CallAudioSession().start() - XCTAssertTrue(rtcAudioSession.useManualAudio) - XCTAssertTrue(rtcAudioSession.isAudioEnabled) - - CallAudioSession().stop() - XCTAssertTrue(rtcAudioSession.useManualAudio) - XCTAssertFalse(rtcAudioSession.isAudioEnabled) - } -} diff --git a/Signal/test/util/SearcherTest.swift b/Signal/test/util/SearcherTest.swift index 0be0cdb44..8e12ac952 100644 --- a/Signal/test/util/SearcherTest.swift +++ b/Signal/test/util/SearcherTest.swift @@ -9,14 +9,15 @@ class SearcherTest: XCTestCase { struct TestCharacter { let name: String let description: String + let phoneNumber: String? } - let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky") - let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity") - let regularLizaveta = TestCharacter(name: "Lizaveta", description: "") + let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky", phoneNumber: nil) + let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity", phoneNumber: "+13235555555") + let regularLizaveta = TestCharacter(name: "Lizaveta", description: "", phoneNumber: "1 (415) 555-5555") let indexer = { (character: TestCharacter) in - return "\(character.name) \(character.description)" + return "\(character.name) \(character.description) \(character.phoneNumber ?? "")" } var searcher: Searcher { @@ -57,4 +58,20 @@ class SearcherTest: XCTestCase { XCTAssert(searcher.matches(item: stinkingLizaveta, query: "Lizaveta St")) XCTAssert(searcher.matches(item: stinkingLizaveta, query: " Lizaveta St ")) } + + func testFormattingChars() { + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"323")) + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"1-323-555-5555")) + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"13235555555")) + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"+1-323")) + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza +1-323")) + + // Sanity check, match both by names + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza")) + XCTAssert(searcher.matches(item: regularLizaveta, query:"Liza")) + + // Disambiguate the two Liza's by area code + XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza 323")) + XCTAssertFalse(searcher.matches(item: regularLizaveta, query:"Liza 323")) + } }