diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/KeyboardSearchViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/KeyboardSearchViewController.swift index 56a4a6a7b5..295d339288 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/KeyboardSearchViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/KeyboardSearchViewController.swift @@ -48,7 +48,7 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat * for associated lexical models (or to indicate that none should occur). Otherwise, the lexical model * search closure provided to the keyboard search will never evaluate. */ - case untagged(KeymanPackage.Key, URL, (DelayedLanguageSelection) -> Void) + case untagged(KeymanPackage.Key, URL) /** * Indicates that a package has been selected for download and is already associated with a @@ -71,7 +71,6 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat private var hasFinalized = false private let keyboardSelectionClosure: SelectionCompletedHandler! - private let lexicalModelSelectionClosure: SelectionCompletedHandler! private let languageCode: String? private let session: URLSession @@ -90,12 +89,10 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat public init(languageCode: String? = nil, withSession session: URLSession = URLSession.shared, - keyboardSelectionBlock: @escaping SelectionCompletedHandler, - lexicalModelSelectionBlock: @escaping SelectionCompletedHandler) { + keyboardSelectionBlock: @escaping SelectionCompletedHandler) { self.languageCode = languageCode self.session = session self.keyboardSelectionClosure = keyboardSelectionBlock - self.lexicalModelSelectionClosure = lexicalModelSelectionBlock super.init(nibName: nil, bundle: nil) } @@ -141,7 +138,6 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat // If the view is being dismissed but no matching link has been intercepted... if self.isMovingFromParent, !hasFinalized { self.keyboardSelectionClosure(.cancelled) - self.lexicalModelSelectionClosure(.cancelled) } } @@ -175,50 +171,13 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat if let lang_id = lang_id { let resourceKey = FullKeyboardID(keyboardID: keyboard_id, languageID: lang_id) let kbdURL = ResourceDownloadManager.shared.defaultDownloadURL(forPackage: packageKey, andResource: resourceKey, asUpdate: false) - performLanguageSearch(languageID: lang_id) self.keyboardSelectionClosure(.tagged(packageKey, kbdURL, resourceKey)) } else { - // No language ID? Defer the language search! - let deferredModelClosure: DeferredLexicalModelSearch = { arg in - if case let .tag(lgCode) = arg { - self.performLanguageSearch(languageID: lgCode) - } else { - self.lexicalModelSelectionClosure(.cancelled) - } - } let kbdURL = ResourceDownloadManager.shared.defaultDownloadURL(forPackage: packageKey, asUpdate: false) - self.keyboardSelectionClosure(.untagged(packageKey, kbdURL, deferredModelClosure)) + self.keyboardSelectionClosure(.untagged(packageKey, kbdURL)) } } - internal func performLanguageSearch(languageID: String) { - if Storage.active.userDefaults.userLexicalModels?.contains(where: { $0.languageID == languageID }) == nil { - Queries.LexicalModel.fetchModels(forLanguageCode: languageID, - withSession: session) { results, error in - if let results = results { - if results.count == 0 { - self.lexicalModelSelectionClosure(.cancelled) - } else { - let lexicalModel = results[0].0 - self.lexicalModelSelectionClosure(.tagged(lexicalModel.packageKey, results[0].1, lexicalModel.fullID)) - } - } else { - // .cancelled because if we already errored once, a redo of the query is likely - // to raise an error again. We -could- defer as if there were no language tag, though. - self.lexicalModelSelectionClosure(.cancelled) - - if let error = error { - log.error("Could not find a lexical model for language id \"\(languageID)\" due to error: \(String(describing: error))") - } - } - } - } else { - // We already have a lexical model for the language. No need to download a new one. - self.lexicalModelSelectionClosure(.cancelled) - } - } - - public static func defaultKeyboardInstallationClosure(installCompletionBlock: ((DefaultInstallationResult) -> Void)? = nil) -> SelectionCompletedHandler { return defaultKeyboardInstallationClosure(withDownloadManager: ResourceDownloadManager.shared, installCompletionBlock: installCompletionBlock) @@ -226,9 +185,7 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat // For unit testing. internal static func defaultKeyboardInstallationClosure(withDownloadManager downloadManager: ResourceDownloadManager, - dispatchGroup: DispatchGroup? = nil, installCompletionBlock: ((DefaultInstallationResult) -> Void)? = nil) -> SelectionCompletedHandler { - dispatchGroup?.enter() // register the closure for group synchronization. // Used to finalize the results of the closure, allowing the callback to complete // before signaling 'group completion' to the DispatchGroup synchronization object. @@ -238,8 +195,6 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat } else if let message = message { log.error(message) } - - dispatchGroup?.leave() // "fulfill" this closure's aspect of the group's synchronization scheme. } return { searchResult in @@ -250,7 +205,7 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat case .cancelled: finalize(as: .cancelled) return - case .untagged(let key, let url, _): + case .untagged(let key, let url): packageKey = key packageURL = url case .tagged(let key, let url, _): @@ -264,14 +219,12 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat let message = "Could not download package \(packageKey): \(errString)" finalize(as: .error(error), noCallbackLog: message) - if case let .untagged(_, _, deferredLexicalModelSearch) = searchResult { - deferredLexicalModelSearch(.none) - } return } + // TODO: needs changes to integrate with AssociatingPackageInstaller switch searchResult { - case .untagged(_, _, let deferredLexicalModelSearch): + case .untagged(_, _): do { // TODO: We don't know which resource the user actually wants. Prompt them. // But for now, the old 'default' installation. @@ -282,10 +235,8 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat // Yeah, it's ugly... but it's best to fix as part of resolution of the TODO above. // That's "the first language pairing of the first keyboard in the package." finalize(as: .success(resourceKey)) - deferredLexicalModelSearch(.tag(resourceKey.languageID)) } catch { finalize(as: .error(error), noCallbackLog: "Could not install package \(packageKey): \(error)") - deferredLexicalModelSearch(.none) throw error // For more accurate notifications } case .tagged(_, _, let resourceKey): @@ -305,59 +256,4 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat downloadManager.downloadPackage(withKey: packageKey, from: packageURL, withNotifications: true, completionBlock: downloadClosure) } } - - public static func defaultLexicalModelInstallationClosure(installCompletionBlock: ((DefaultInstallationResult) -> Void)? = nil) -> SelectionCompletedHandler { - return defaultLexicalModelInstallationClosure(withDownloadManager: ResourceDownloadManager.shared) - } - - // For unit testing. - internal static func defaultLexicalModelInstallationClosure(withDownloadManager downloadManager: ResourceDownloadManager, - dispatchGroup: DispatchGroup? = nil, - installCompletionBlock: ((DefaultInstallationResult) -> Void)? = nil) -> SelectionCompletedHandler { - dispatchGroup?.enter() // register the closure for group synchronization. - - // Used to finalize the results of the closure, allowing the callback to complete - // before signaling 'group completion' to the DispatchGroup synchronization object. - func finalize(as result: DefaultInstallationResult, noCallbackLog message: String? = nil) { - if let installCompletionBlock = installCompletionBlock { - installCompletionBlock(result) - } else if let message = message { - log.error(message) - } - - dispatchGroup?.leave() // "fulfill" this closure's aspect of the group's synchronization scheme. - } - - return { searchResult in - switch searchResult { - case .cancelled: - finalize(as: .cancelled) - return - case .tagged(let packageKey, let packageURL, let resourceKey): - var lmInstallClosure: ResourceDownloadManager.CompletionHandler - lmInstallClosure = { package, error in - if let package = package, error == nil { - // perform the actual installation - do { - try ResourceFileManager.shared.install(resourceWithID: resourceKey, from: package) - finalize(as: .success(resourceKey)) - } catch { - finalize(as: .error(error), noCallbackLog: "Could not install \(resourceKey) from package \(packageKey): \(error)") - throw error - } - } else { - let errString = error != nil ? String(describing: error) : "" - let message = "Could not download package \(packageKey): \(errString)" - - finalize(as: .error(error), noCallbackLog: message) - throw error ?? NSError() - } - } - - downloadManager.downloadPackage(withKey: packageKey, from: packageURL, withNotifications: true, completionBlock: lmInstallClosure) - case .untagged(_, _, _): - fatalError() // Explicitly illegal state, as documented in the enum's definition. - } - } - } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Settings/InstalledLanguagesViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Settings/InstalledLanguagesViewController.swift index ce5d74dc9a..bf8af69a5f 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Settings/InstalledLanguagesViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Settings/InstalledLanguagesViewController.swift @@ -431,8 +431,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie extension InstalledLanguagesViewController { @objc func addClicked(_ sender: Any) { - let keyboardSearchVC = KeyboardSearchViewController(keyboardSelectionBlock: KeyboardSearchViewController.defaultKeyboardInstallationClosure(), - lexicalModelSelectionBlock: KeyboardSearchViewController.defaultLexicalModelInstallationClosure()) + let keyboardSearchVC = KeyboardSearchViewController(keyboardSelectionBlock: KeyboardSearchViewController.defaultKeyboardInstallationClosure()) navigationController!.pushViewController(keyboardSearchVC, animated: true) } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift index 217938828b..690b915388 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Settings/LanguageSettingsViewController.swift @@ -301,8 +301,7 @@ class LanguageSettingsViewController: UITableViewController { @objc func addClicked(_ sender: Any) { let keyboardSearchVC = KeyboardSearchViewController(languageCode: self.language.id, - keyboardSelectionBlock: KeyboardSearchViewController.defaultKeyboardInstallationClosure(), - lexicalModelSelectionBlock: KeyboardSearchViewController.defaultLexicalModelInstallationClosure()) + keyboardSelectionBlock: KeyboardSearchViewController.defaultKeyboardInstallationClosure()) navigationController!.pushViewController(keyboardSearchVC, animated: true) } diff --git a/ios/engine/KMEI/KeymanEngineTests/KeyboardSearchTests.swift b/ios/engine/KMEI/KeymanEngineTests/KeyboardSearchTests.swift index 4c06895b4f..776b39ad0b 100644 --- a/ios/engine/KMEI/KeymanEngineTests/KeyboardSearchTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/KeyboardSearchTests.swift @@ -68,64 +68,29 @@ class KeyboardSearchTests: XCTestCase { func testFinalizeNoLanguage() { let kbdExpectation = XCTestExpectation() - let lmExpectation = XCTestExpectation() - let groupExpectation = XCTestExpectation() - let dispatchGroup = DispatchGroup() - - dispatchGroup.enter() - let noLangKbdBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in + let kbdBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in switch searchResult { - case .untagged(let packageKey, let url, let langTaggingClosure): + case .untagged(let packageKey, let url): XCTAssertEqual(packageKey, TestUtils.Packages.Keys.khmer_angkor) XCTAssertNotNil(url) - - langTaggingClosure(.none) default: XCTFail() } kbdExpectation.fulfill() - dispatchGroup.leave() - } - - dispatchGroup.enter() - let noLangLMBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in - switch searchResult { - case .cancelled: - break - default: - XCTFail() - } - - lmExpectation.fulfill() - dispatchGroup.leave() - } - - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() } let searchNoLang = KeyboardSearchViewController(languageCode: nil, withSession: mockedURLSession!, - keyboardSelectionBlock: noLangKbdBlock, - lexicalModelSelectionBlock: noLangLMBlock) + keyboardSelectionBlock: kbdBlock) searchNoLang.finalize(with: "khmer_angkor", for: nil) - wait(for: [kbdExpectation, lmExpectation, groupExpectation], timeout: 5) + wait(for: [kbdExpectation], timeout: 5) } func testFinalizeWithLanguage() { - let mockedModelQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.model_case_en, error: nil) - mockedURLSession?.queueMockResult(.data(mockedModelQuery)) - let kbdExpectation = XCTestExpectation() - let lmExpectation = XCTestExpectation() - let groupExpectation = XCTestExpectation() - - let dispatchGroup = DispatchGroup() - - dispatchGroup.enter() let kbdBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in switch searchResult { case .tagged(let packageKey, let url, let resourceKey): @@ -137,371 +102,118 @@ class KeyboardSearchTests: XCTestCase { } kbdExpectation.fulfill() - dispatchGroup.leave() } - dispatchGroup.enter() - let lmBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in - switch searchResult { - case .tagged(let packageKey, let url, let resourceKey): - XCTAssertEqual(packageKey, TestUtils.Packages.Keys.nrc_en_mtnt) - XCTAssertNotNil(url) - XCTAssertEqual(resourceKey, TestUtils.LexicalModels.mtnt.fullID) - default: - XCTFail() - } - - lmExpectation.fulfill() - dispatchGroup.leave() - } - - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - let searchNoLang = KeyboardSearchViewController(languageCode: nil, + let search = KeyboardSearchViewController(languageCode: nil, withSession: mockedURLSession!, - keyboardSelectionBlock: kbdBlock, - lexicalModelSelectionBlock: lmBlock) - searchNoLang.finalize(with: "sil_euro_latin", for: "en") + keyboardSelectionBlock: kbdBlock) + search.finalize(with: "sil_euro_latin", for: "en") - wait(for: [kbdExpectation, lmExpectation, groupExpectation], timeout: 5) + wait(for: [kbdExpectation], timeout: 5) } - func testFinalizeWithLanguageModelPreinstalled() throws { - guard let mtntPackage = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) as? LexicalModelKeymanPackage else { - XCTFail() - return - } - try ResourceFileManager.shared.install(resourceWithID: TestUtils.LexicalModels.mtnt.fullID, from: mtntPackage) - - let kbdExpectation = XCTestExpectation() - let lmExpectation = XCTestExpectation() - let groupExpectation = XCTestExpectation() - - let dispatchGroup = DispatchGroup() - - dispatchGroup.enter() - let kbdBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in - switch searchResult { - case .tagged(let packageKey, let url, let resourceKey): - XCTAssertEqual(packageKey, TestUtils.Packages.Keys.sil_euro_latin) - XCTAssertNotNil(url) - XCTAssertEqual(resourceKey, TestUtils.Keyboards.sil_euro_latin.fullID) - default: - XCTFail() - } - - kbdExpectation.fulfill() - dispatchGroup.leave() - } - - dispatchGroup.enter() - let lmBlock: KeyboardSearchViewController.SelectionCompletedHandler = { searchResult in - switch searchResult { - case .cancelled: - break - default: - XCTFail() - } - - lmExpectation.fulfill() - dispatchGroup.leave() - } - - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - let searchNoLang = KeyboardSearchViewController(languageCode: nil, - withSession: mockedURLSession!, - keyboardSelectionBlock: kbdBlock, - lexicalModelSelectionBlock: lmBlock) - searchNoLang.finalize(with: "sil_euro_latin", for: "en") - - wait(for: [kbdExpectation, lmExpectation, groupExpectation], timeout: 5) - } - - func testDefaultKeyboardInstallationClosureTaggedSuccess() throws { - // Step 1: mocking. - let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: nil) - mockedURLSession!.queueMockResult(.download(packageDownloadTask)) - - let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure & synchronization check - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case let .success(fullID) = result { - XCTAssertEqual(fullID as? FullKeyboardID, TestUtils.Keyboards.khmer_angkor.fullID) - } else { - XCTFail("keyboard installation did not succeed.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.tagged(TestUtils.Packages.Keys.khmer_angkor, TestUtils.Keyboards.khmerAngkorKMP, TestUtils.Keyboards.khmer_angkor.fullID)) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - - // Step 4 - verify installation - XCTAssertTrue(Storage.active.userDefaults.userKeyboards!.contains { $0.fullID == TestUtils.Keyboards.khmer_angkor.fullID }) - XCTAssertNotNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.khmer_angkor)) - } - - func testDefaultKeyboardInstallationClosureTaggedError() throws { - // Step 1: mocking. - let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: TestUtils.mockedError) - mockedURLSession!.queueMockResult(.download(packageDownloadTask)) - - let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure & synchronization check - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case .error(_) = result { - // Success! - } else { - XCTFail("keyboard installation did not result in mocked error.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.tagged(TestUtils.Packages.Keys.khmer_angkor, TestUtils.Keyboards.khmerAngkorKMP, TestUtils.Keyboards.khmer_angkor.fullID)) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - - // Step 4 - verify lack of installation - XCTAssertFalse(Storage.active.userDefaults.userKeyboards?.contains { $0.fullID == TestUtils.Keyboards.khmer_angkor.fullID } ?? false) - XCTAssertNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.khmer_angkor)) - } - - func testDefaultKeyboardInstallationClosureTaggedCancel() throws { - // Step 1: mocking. - let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure & synchronization check - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case .cancelled = result { - // Success! - } else { - XCTFail("keyboard-search cancellation handled improperly.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.cancelled) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - } - - func testDefaultLexicalModelInstallationClosureTaggedSuccess() throws { - // Step 1 - mocking - let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.LexicalModels.mtntKMP, error: nil) - mockedURLSession!.queueMockResult(.download(packageDownloadTask)) - - let installExpectation = XCTestExpectation(description: "Lexical model package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultLexicalModelInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case let .success(fullID) = result { - XCTAssertEqual(fullID as? FullLexicalModelID, TestUtils.LexicalModels.mtnt.fullID) - } else { - XCTFail("Lexical model installation did not succeed.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.tagged(TestUtils.Packages.Keys.nrc_en_mtnt, TestUtils.LexicalModels.mtntKMP, TestUtils.LexicalModels.mtnt.fullID)) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - - // Step 4: Verify installation - XCTAssertTrue(Storage.active.userDefaults.userLexicalModels!.contains { $0.fullID == TestUtils.LexicalModels.mtnt.fullID }) - XCTAssertNotNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.nrc_en_mtnt)) - } - - func testDefaultLexicalModelInstallationClosureTaggedError() throws { - // Step 1 - mocking - let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.LexicalModels.mtntKMP, error: TestUtils.mockedError) - mockedURLSession!.queueMockResult(.download(packageDownloadTask)) - - let installExpectation = XCTestExpectation(description: "Lexical model package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultLexicalModelInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case .error(_) = result { - // Mocked success! - } else { - XCTFail("Lexical model installation did not generated mocked error.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.tagged(TestUtils.Packages.Keys.nrc_en_mtnt, TestUtils.LexicalModels.mtntKMP, TestUtils.LexicalModels.mtnt.fullID)) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - - // Step 4: Verify lack of installation - XCTAssertFalse(Storage.active.userDefaults.userLexicalModels?.contains { $0.fullID == TestUtils.LexicalModels.mtnt.fullID } ?? false) - XCTAssertNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.nrc_en_mtnt)) - } - - func testDefaultLexicalModelInstallationClosureTaggedCancel() throws { - let installExpectation = XCTestExpectation(description: "Lexical model package download & installation should complete") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closure - let dispatchGroup = DispatchGroup() - - let closure = KeyboardSearchViewController.defaultLexicalModelInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case .cancelled = result { - // Mocked success! - } else { - XCTFail("Keyboard-search model cancellation handled improperly.") - } - - installExpectation.fulfill() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - closure(.cancelled) - - wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) - } - - func testDeferredLexicalModelSearch() { - // Step 1: mocking. - let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: nil) - mockedURLSession!.queueMockResult(.download(packageDownloadTask)) - - let kbdExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") - let lmExpectation = XCTestExpectation(description: "Deferred lexical model search should be triggered") - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - - // Step 2 - build closures & synchronization check - let dispatchGroup = DispatchGroup() - - let kbdClosure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in - if case let .success(fullID) = result { - XCTAssertEqual(fullID as? FullKeyboardID, TestUtils.Keyboards.khmer_angkor.fullID) - } else { - XCTFail("keyboard installation did not succeed.") - } - - kbdExpectation.fulfill() - } - - // This whole shebang is to mock the "untagged" closure and ensure it is properly called - // when a language tag is discovered at/after package install, rather than at download time. - dispatchGroup.enter() - let lmCallClosure: KeyboardSearchViewController.DeferredLexicalModelSearch = { param in - switch param { - case .tag(let tag): - XCTAssertEqual(tag, "km") - default: - XCTFail() - } - - lmExpectation.fulfill() - dispatchGroup.leave() - } - - // As the closure has now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - groupExpectation.fulfill() - } - - // Step 3 - run closure - kbdClosure(.untagged(TestUtils.Packages.Keys.khmer_angkor, TestUtils.Keyboards.khmerAngkorKMP, lmCallClosure)) - - wait(for: [kbdExpectation, lmExpectation, groupExpectation], timeout: 5, enforceOrder: true) - } - - // Synchronization test: using both closures TOGETHER. - func testDefaultClosureSynchronization() { - let kbdExpectation = XCTestExpectation() - let lmExpectation = XCTestExpectation() - let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") - let dispatchGroup = DispatchGroup() - - let kbdClosure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager) { _ in kbdExpectation.fulfill() } - let lmClosure = KeyboardSearchViewController.defaultLexicalModelInstallationClosure(withDownloadManager: downloadManager) { _ in lmExpectation.fulfill() } - - // As the closures have now been built (and thus, DispatchGroup.enter() called), - // notification only occurs the callback completes. - dispatchGroup.notify(queue: .main) { - // Signals that both have completed. - groupExpectation.fulfill() - } - - // Step 3 - run closure - kbdClosure(.cancelled) - lmClosure(.cancelled) - - // These calls will technically be synchronous, so we can rely on kbd and lm to be - // in the correct order. The key is that the groupExpectation is only fulfilled after - // the other two. - wait(for: [kbdExpectation, lmExpectation, groupExpectation], timeout: 5, enforceOrder: true) - } +// +// func testDefaultKeyboardInstallationClosureTaggedSuccess() throws { +// // Step 1: mocking. +// let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: nil) +// mockedURLSession!.queueMockResult(.download(packageDownloadTask)) +// +// let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") +// let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") +// +// // Step 2 - build closure & synchronization check +// let dispatchGroup = DispatchGroup() +// +// let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in +// if case let .success(fullID) = result { +// XCTAssertEqual(fullID as? FullKeyboardID, TestUtils.Keyboards.khmer_angkor.fullID) +// } else { +// XCTFail("keyboard installation did not succeed.") +// } +// +// installExpectation.fulfill() +// } +// +// // As the closure has now been built (and thus, DispatchGroup.enter() called), +// // notification only occurs the callback completes. +// dispatchGroup.notify(queue: .main) { +// groupExpectation.fulfill() +// } +// +// // Step 3 - run closure +// closure(.tagged(TestUtils.Packages.Keys.khmer_angkor, TestUtils.Keyboards.khmerAngkorKMP, TestUtils.Keyboards.khmer_angkor.fullID)) +// +// wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) +// +// // Step 4 - verify installation +// XCTAssertTrue(Storage.active.userDefaults.userKeyboards!.contains { $0.fullID == TestUtils.Keyboards.khmer_angkor.fullID }) +// XCTAssertNotNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.khmer_angkor)) +// } +// +// func testDefaultKeyboardInstallationClosureTaggedError() throws { +// // Step 1: mocking. +// let packageDownloadTask = TestUtils.Downloading.MockResult(location: TestUtils.Keyboards.khmerAngkorKMP, error: TestUtils.mockedError) +// mockedURLSession!.queueMockResult(.download(packageDownloadTask)) +// +// let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") +// let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") +// +// // Step 2 - build closure & synchronization check +// let dispatchGroup = DispatchGroup() +// +// let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in +// if case .error(_) = result { +// // Success! +// } else { +// XCTFail("keyboard installation did not result in mocked error.") +// } +// +// installExpectation.fulfill() +// } +// +// // As the closure has now been built (and thus, DispatchGroup.enter() called), +// // notification only occurs the callback completes. +// dispatchGroup.notify(queue: .main) { +// groupExpectation.fulfill() +// } +// +// // Step 3 - run closure +// closure(.tagged(TestUtils.Packages.Keys.khmer_angkor, TestUtils.Keyboards.khmerAngkorKMP, TestUtils.Keyboards.khmer_angkor.fullID)) +// +// wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) +// +// // Step 4 - verify lack of installation +// XCTAssertFalse(Storage.active.userDefaults.userKeyboards?.contains { $0.fullID == TestUtils.Keyboards.khmer_angkor.fullID } ?? false) +// XCTAssertNil(ResourceFileManager.shared.getInstalledPackage(withKey: TestUtils.Packages.Keys.khmer_angkor)) +// } +// +// func testDefaultKeyboardInstallationClosureTaggedCancel() throws { +// // Step 1: mocking. +// let installExpectation = XCTestExpectation(description: "Keyboard package download & installation should complete") +// let groupExpectation = XCTestExpectation(description: "DispatchGroup should notify") +// +// // Step 2 - build closure & synchronization check +// let dispatchGroup = DispatchGroup() +// +// let closure = KeyboardSearchViewController.defaultKeyboardInstallationClosure(withDownloadManager: downloadManager, dispatchGroup: dispatchGroup) { result in +// if case .cancelled = result { +// // Success! +// } else { +// XCTFail("keyboard-search cancellation handled improperly.") +// } +// +// installExpectation.fulfill() +// } +// +// // As the closure has now been built (and thus, DispatchGroup.enter() called), +// // notification only occurs the callback completes. +// dispatchGroup.notify(queue: .main) { +// groupExpectation.fulfill() +// } +// +// // Step 3 - run closure +// closure(.cancelled) +// +// wait(for: [installExpectation, groupExpectation], timeout: 5, enforceOrder: true) +// } }