Better search results

This commit is contained in:
Yishi Lin 2020-02-23 18:05:10 +08:00
parent 0eb4b01fb7
commit 6bf4716366
6 changed files with 160 additions and 136 deletions

View file

@ -105,6 +105,8 @@
A267002A1EEC466A00176B8A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A26700281EEC466A00176B8A /* MainInterface.storyboard */; };
A267002E1EEC466A00176B8A /* passExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A26700241EEC466A00176B8A /* passExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
A26700371EEC475600176B8A /* passProcessor.js in Resources */ = {isa = PBXBuildFile; fileRef = A26700351EEC475600176B8A /* passProcessor.js */; };
A2699ACD2402631400F36323 /* PasswordTableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2699ACC2402631400F36323 /* PasswordTableEntry.swift */; };
A2699ACF24027D9500F36323 /* PasswordTableEntryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */; };
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
A2A61C131EEF90CB00CFE063 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
@ -327,6 +329,8 @@
A26700321EEC46C400176B8A /* pass.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pass.entitlements; sourceTree = "<group>"; };
A26700331EEC46C900176B8A /* passExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = passExtension.entitlements; sourceTree = "<group>"; };
A26700351EEC475600176B8A /* passProcessor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = passProcessor.js; sourceTree = "<group>"; };
A2699ACC2402631400F36323 /* PasswordTableEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTableEntry.swift; sourceTree = "<group>"; };
A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTableEntryTest.swift; sourceTree = "<group>"; };
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
A2A61C1F1EEFABAD00CFE063 /* UtilsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilsExtension.swift; sourceTree = "<group>"; };
@ -545,6 +549,7 @@
30C015A7214ED378005BB6DF /* Models */ = {
isa = PBXGroup;
children = (
A2699ACE24027D9500F36323 /* PasswordTableEntryTest.swift */,
30B0485F209A5141001013CA /* PasswordTest.swift */,
);
path = Models;
@ -661,6 +666,7 @@
30697C4021F63CAB0064FCAC /* Password.swift */,
30697C3F21F63CAA0064FCAC /* PasswordEntity.swift */,
30697C4321F63CAB0064FCAC /* PasswordStore.swift */,
A2699ACC2402631400F36323 /* PasswordTableEntry.swift */,
);
path = Models;
sourceTree = "<group>";
@ -1323,6 +1329,7 @@
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */,
30697C4721F63CAB0064FCAC /* PasscodeLock.swift in Sources */,
A2699ACD2402631400F36323 /* PasswordTableEntry.swift in Sources */,
30697C3421F63C8B0064FCAC /* PasscodeLockViewController.swift in Sources */,
3087574F2343E42A00B971A2 /* Colors.swift in Sources */,
30697C2C21F63C5A0064FCAC /* FileManagerExtension.swift in Sources */,
@ -1345,6 +1352,7 @@
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
A2699ACF24027D9500F36323 /* PasswordTableEntryTest.swift in Sources */,
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */,
A2AA934622DE3A8000D79A00 /* PGPAgentTest.swift in Sources */,
30BAC8C622E3BAAF00438475 /* TestBase.swift in Sources */,

View file

@ -10,23 +10,11 @@ import UIKit
import SVProgressHUD
import passKit
fileprivate class PasswordsTableEntry : NSObject {
@objc var title: String
var isDir: Bool
var passwordEntity: PasswordEntity?
init(title: String, isDir: Bool, passwordEntity: PasswordEntity?) {
self.title = title
self.isDir = isDir
self.passwordEntity = passwordEntity
}
}
fileprivate let hideSectionHeaderTreshold = 6 // hide section header if passwords count is less than the threshold
fileprivate let hideSectionHeaderThreshold = 6 // hide section header if passwords count is less than the threshold
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableAllEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableEntries: [PasswordTableEntry] = []
private var passwordsTableAllEntries: [PasswordTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil
private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared
@ -34,9 +22,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private var tapTabBarTime: TimeInterval = 0
private var tapNavigationBarGestureRecognizer: UITapGestureRecognizer!
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
private var sections = [(title: String, entries: [PasswordTableEntry])]()
private var searchActive : Bool = false
private enum PasswordLabel {
case all
case unsynced
@ -130,23 +117,18 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
@IBOutlet weak var tableView: UITableView!
private func initPasswordsTableEntries(parent: PasswordEntity?) {
passwordsTableEntries.removeAll()
passwordsTableAllEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
var passwordEntities = [PasswordEntity]()
var passwordAllEntities = [PasswordEntity]()
if Defaults.isShowFolderOn {
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
} else {
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
let passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableAllEntries = passwordAllEntities.compactMap {
PasswordTableEntry($0)
}
passwordsTableEntries = passwordEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
}
passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableAllEntries = passwordAllEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
let passwordEntities = Defaults.isShowFolderOn ?
self.passwordStore.fetchPasswordEntityCoreData(parent: parent) :
passwordAllEntities
passwordsTableEntries = passwordEntities.compactMap {
PasswordTableEntry($0)
}
parentPasswordEntity = parent
}
@ -186,7 +168,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "SyncingPasswordStore".localize())
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: {(git_transfer_progress, stop) in
@ -286,10 +267,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
self.reloadTableView(parent: nil, label: .all)
}
let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in
self.filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
return !entry.passwordEntity!.synced
let filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
return !entry.synced
}
self.reloadTableView(data: self.filteredPasswordsTableEntries, label: .unsynced)
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
@ -343,7 +324,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return recognizer
}()
let entry = getPasswordEntry(by: indexPath)
let passwordEntity = entry.passwordEntity!
let passwordEntity = entry.passwordEntity
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
cell.textLabel?.text = passwordEntity.synced ? entry.title : "\(entry.title)"
@ -367,7 +348,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return cell
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
return sections[indexPath.section].entries[indexPath.row]
}
@ -420,7 +401,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
private func hideSectionHeader() -> Bool {
if passwordsTableEntries.count < hideSectionHeaderTreshold || self.searchController.isActive {
if passwordsTableEntries.count < hideSectionHeaderThreshold || self.searchController.isActive {
return true
}
return false
@ -475,7 +456,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
Utils.alert(title: "CannotCopyPassword".localize(), message: "PgpKeyNotSet.".localize(), controller: self, completion: nil)
return
}
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity!
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
SVProgressHUD.dismiss()
DispatchQueue.global(qos: .userInteractive).async {
@ -497,27 +478,27 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
private func generateSections(item: [PasswordsTableEntry]) {
private func generateSections(item: [PasswordTableEntry]) {
let collation = UILocalizedIndexedCollation.current()
let sectionTitles = collation.sectionIndexTitles
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
var newSections = [(title: String, entries: [PasswordTableEntry])]()
// initialize all sections
for i in 0..<sectionTitles.count {
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
newSections.append((title: sectionTitles[i], entries: [PasswordTableEntry]()))
}
// put entries into sections
for entry in item {
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordTableEntry.title))
newSections[sectionNumber].entries.append(entry)
}
// sort each list and set sectionTitles
for i in 0..<sectionTitles.count {
let entriesToSort = newSections[i].entries
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordTableEntry.title))
newSections[i].entries = sortedEntries as! [PasswordTableEntry]
}
// only keep non-empty sections
@ -552,7 +533,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
if segue.identifier == "showPasswordDetail" {
if let viewController = segue.destination as? PasswordDetailTableViewController {
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity!
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity
viewController.passwordEntity = passwordEntity
}
} else if segue.identifier == "addPasswordSegue" {
@ -567,32 +548,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
switch scope {
case .all:
filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in
let name = entry.passwordEntity?.nameWithCategory ?? entry.title
return name.localizedCaseInsensitiveContains(searchText)
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableAllEntries)
}
case .current:
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
return entry.title.lowercased().contains(searchText.lowercased())
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableEntries)
}
var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries
if searchController.isActive && searchController.searchBar.text != "" {
entries = entries.filter {$0.match(searchText)}
}
reloadTableView(data: entries)
}
private func reloadTableView(data: [PasswordsTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) {
private func reloadTableView(data: [PasswordTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) {
// set navigation item
if passwordStore.numberOfLocalCommits != 0 {
navigationController?.tabBarItem.badgeValue = "\(passwordStore.numberOfLocalCommits)"

View file

@ -9,19 +9,6 @@
import AuthenticationServices
import passKit
fileprivate class PasswordsTableEntry : NSObject {
var title: String
var categoryText: String
var categoryArray: [String]
var passwordEntity: PasswordEntity?
init(_ entity: PasswordEntity) {
self.title = entity.name!
self.categoryText = entity.getCategoryText()
self.categoryArray = entity.getCategoryArray()
self.passwordEntity = entity
}
}
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
@ -30,8 +17,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
private let keychain = AppKeychain.shared
private var searchActive = false
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableEntries: [PasswordTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordTableEntry] = []
private lazy var passcodelock: PasscodeExtensionDisplay = {
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
@ -118,12 +105,11 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
}
private func initPasswordsTableEntries() {
passwordsTableEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
var passwordEntities = [PasswordEntity]()
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.map {
PasswordsTableEntry($0)
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.compactMap {
PasswordTableEntry($0)
}
}
@ -131,14 +117,14 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath)
if entry.passwordEntity!.synced {
if entry.passwordEntity.synced {
cell.textLabel?.text = entry.title
} else {
cell.textLabel?.text = "\(entry.title)"
}
cell.accessoryType = .none
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
cell.detailTextLabel?.text = entry.passwordEntity?.getCategoryText()
cell.detailTextLabel?.text = entry.categoryText
return cell
}
@ -151,7 +137,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
return
}
let passwordEntity = entry.passwordEntity!
let passwordEntity = entry.passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
DispatchQueue.global(qos: .userInteractive).async {
var decryptedPassword: Password?
@ -212,16 +198,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, searchText.isEmpty == false {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
var matched = false
matched = matched || entry.title.range(of: searchText, options: .caseInsensitive) != nil
matched = matched || searchText.range(of: entry.title, options: .caseInsensitive) != nil
entry.categoryArray.forEach({ (category) in
matched = matched || category.range(of: searchText, options: .caseInsensitive) != nil
matched = matched || searchText.range(of: category, options: .caseInsensitive) != nil
})
return matched
}
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
searchActive = true
} else {
searchActive = false
@ -233,7 +210,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
searchBarSearchButtonClicked(searchBar)
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
if searchActive {
return filteredPasswordsTableEntries[indexPath.row]
} else {

View file

@ -10,19 +10,6 @@ import Foundation
import MobileCoreServices
import passKit
fileprivate class PasswordsTableEntry : NSObject {
var title: String
var categoryText: String
var categoryArray: [String]
var passwordEntity: PasswordEntity?
init(_ entity: PasswordEntity) {
self.title = entity.name!
self.categoryText = entity.getCategoryText()
self.categoryArray = entity.getCategoryArray()
self.passwordEntity = entity
}
}
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
@ -31,8 +18,8 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
private let keychain = AppKeychain.shared
private var searchActive = false
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableEntries: [PasswordTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordTableEntry] = []
enum Action {
case findLogin, fillBrowser, unknown
@ -46,12 +33,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}()
private func initPasswordsTableEntries() {
passwordsTableEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
var passwordEntities = [PasswordEntity]()
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.map {
PasswordsTableEntry($0)
PasswordTableEntry($0)
}
}
@ -139,7 +125,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath)
if entry.passwordEntity!.synced {
if entry.synced {
cell.textLabel?.text = entry.title
} else {
cell.textLabel?.text = "\(entry.title)"
@ -159,7 +145,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
return
}
let passwordEntity = entry.passwordEntity!
let passwordEntity = entry.passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
DispatchQueue.global(qos: .userInteractive).async {
var decryptedPassword: Password?
@ -243,16 +229,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, searchText.isEmpty == false {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
var matched = false
matched = matched || entry.title.range(of: searchText, options: .caseInsensitive) != nil
matched = matched || searchText.range(of: entry.title, options: .caseInsensitive) != nil
entry.categoryArray.forEach({ (category) in
matched = matched || category.range(of: searchText, options: .caseInsensitive) != nil
matched = matched || searchText.range(of: category, options: .caseInsensitive) != nil
})
return matched
}
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
searchActive = true
} else {
searchActive = false
@ -264,7 +241,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
searchBarSearchButtonClicked(searchBar)
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
if searchActive {
return filteredPasswordsTableEntries[indexPath.row]
} else {

View file

@ -0,0 +1,44 @@
//
// PasswordTableEntry.swift
// passKit
//
// Created by Yishi Lin on 2020/2/23.
// Copyright © 2020 Bob Sun. All rights reserved.
//
import Foundation
public class PasswordTableEntry: NSObject {
public let passwordEntity: PasswordEntity
@objc public let title: String
public let isDir: Bool
public let synced: Bool
public let categoryText: String
public init(_ entity: PasswordEntity) {
self.passwordEntity = entity
self.title = entity.name!
self.isDir = entity.isDir
self.synced = entity.synced
self.categoryText = entity.getCategoryText()
}
public func match(_ searchText: String) -> Bool {
return PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText)
}
public static func match(nameWithCategory: String, searchText: String) -> Bool {
let titleSplit = nameWithCategory.split{ !($0.isLetter || $0.isNumber || $0 == ".") }
for str in titleSplit {
if (str.localizedCaseInsensitiveContains(searchText)) {
return true
}
if (searchText.localizedCaseInsensitiveContains(str)) {
return true
}
}
return false
}
}

View file

@ -0,0 +1,55 @@
//
// PasswordTableEntryTest.swift
// passKitTests
//
// Created by Yishi Lin on 2020/2/23.
// Copyright © 2020 Bob Sun. All rights reserved.
//
import XCTest
@testable import passKit
class PasswordTableEntryTest: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
let nameWithCategoryList = [
"github",
"github.com",
"www.github.com",
"personal/github",
"personal/github.com",
"personal/www.github.com",
"github/personal",
"github.com/personal",
"www.github.com/personal",
"github (personal)",
]
let searchTextList1 = [
"github.com",
"www.github.com"
]
let searchTextList2 = [
"xx.com",
"www.xx.com"
]
for nameWithCategory in nameWithCategoryList {
for searchText in searchTextList1 {
XCTAssertTrue(PasswordTableEntry.match(nameWithCategory: nameWithCategory, searchText: searchText))
}
for searchText in searchTextList2 {
XCTAssertFalse(PasswordTableEntry.match(nameWithCategory: nameWithCategory, searchText: searchText))
}
}
}
}