refactor(ios/engine): rips out search lex-model querying

This commit is contained in:
jahorton 2020-08-06 12:53:19 +07:00
parent 576baf3e00
commit bc061ebda6
4 changed files with 120 additions and 514 deletions

View file

@ -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<FullKeyboardID>!
private let lexicalModelSelectionClosure: SelectionCompletedHandler<FullLexicalModelID>!
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<FullKeyboardID>,
lexicalModelSelectionBlock: @escaping SelectionCompletedHandler<FullLexicalModelID>) {
keyboardSelectionBlock: @escaping SelectionCompletedHandler<FullKeyboardID>) {
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<FullKeyboardID> {
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<FullKeyboardID> {
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<FullLexicalModelID> {
return defaultLexicalModelInstallationClosure(withDownloadManager: ResourceDownloadManager.shared)
}
// For unit testing.
internal static func defaultLexicalModelInstallationClosure(withDownloadManager downloadManager: ResourceDownloadManager,
dispatchGroup: DispatchGroup? = nil,
installCompletionBlock: ((DefaultInstallationResult) -> Void)? = nil) -> SelectionCompletedHandler<FullLexicalModelID> {
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<LexicalModelKeymanPackage>
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) : "<unknown 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.
}
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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<FullKeyboardID> = { searchResult in
let kbdBlock: KeyboardSearchViewController.SelectionCompletedHandler<FullKeyboardID> = { 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<FullLexicalModelID> = { 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<FullKeyboardID> = { 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<FullLexicalModelID> = { 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<FullKeyboardID> = { 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<FullLexicalModelID> = { 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)
// }
}