Repo // Make UserDefaults unit-testable.

This commit is contained in:
ShikiSuen 2023-08-24 22:12:09 +08:00
parent c5c99894a9
commit 3a8060bf88
11 changed files with 67 additions and 32 deletions

View File

@ -81,8 +81,8 @@ open class CtlCandidate: NSWindowController, CtlCandidateProtocol {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
open var candidateFont = NSFont.systemFont( open var candidateFont: NSFont = NSFont.systemFont(
ofSize: min(196, max(12, Double(UserDefaults.standard.integer(forKey: "CandidateListTextSize")))) ofSize: min(196, max(12, Double(UserDefaults.current.integer(forKey: "CandidateListTextSize"))))
) )
@discardableResult open func showNextLine() -> Bool { @discardableResult open func showNextLine() -> Bool {

View File

@ -133,7 +133,7 @@ public extension NSApplication {
let appearanceDescription = NSApp.effectiveAppearance.debugDescription let appearanceDescription = NSApp.effectiveAppearance.debugDescription
.lowercased() .lowercased()
return appearanceDescription.contains("dark") return appearanceDescription.contains("dark")
} else if let appleInterfaceStyle = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") { } else if let appleInterfaceStyle = UserDefaults.current.string(forKey: "AppleInterfaceStyle") {
return appleInterfaceStyle.lowercased().contains("dark") return appleInterfaceStyle.lowercased().contains("dark")
} }
return false return false

View File

@ -49,14 +49,6 @@ public enum IMKHelper {
"org.unknown.keylayout.vChewingMiTAC", "org.unknown.keylayout.vChewingMiTAC",
] ]
public static var currentBasicKeyboardLayout: String {
UserDefaults.standard.string(forKey: "BasicKeyboardLayout") ?? ""
}
public static var isDynamicBasicKeyboardLayoutEnabled: Bool {
Self.arrDynamicBasicKeyLayouts.contains(currentBasicKeyboardLayout) || !currentBasicKeyboardLayout.isEmpty
}
public static var allowedAlphanumericalTISInputSources: [TISInputSource] { public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) } arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
} }

View File

@ -230,22 +230,22 @@ public extension LMMgr {
{ {
return userDictPathDefault return userDictPathDefault
} }
if UserDefaults.standard.object(forKey: UserDef.kUserDataFolderSpecified.rawValue) != nil { if UserDefaults.current.object(forKey: UserDef.kUserDataFolderSpecified.rawValue) != nil {
BookmarkManager.shared.loadBookmarks() BookmarkManager.shared.loadBookmarks()
if Self.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) { if Self.checkIfSpecifiedUserDataFolderValid(userDictPathSpecified) {
return userDictPathSpecified return userDictPathSpecified
} }
UserDefaults.standard.removeObject(forKey: UserDef.kUserDataFolderSpecified.rawValue) UserDefaults.current.removeObject(forKey: UserDef.kUserDataFolderSpecified.rawValue)
} }
return userDictPathDefault return userDictPathDefault
} }
static func cassettePath() -> String { static func cassettePath() -> String {
let rawCassettePath = PrefMgr.shared.cassettePath let rawCassettePath = PrefMgr.shared.cassettePath
if UserDefaults.standard.object(forKey: UserDef.kCassettePath.rawValue) != nil { if UserDefaults.current.object(forKey: UserDef.kCassettePath.rawValue) != nil {
BookmarkManager.shared.loadBookmarks() BookmarkManager.shared.loadBookmarks()
if Self.checkCassettePathValidity(rawCassettePath) { return rawCassettePath } if Self.checkCassettePathValidity(rawCassettePath) { return rawCassettePath }
UserDefaults.standard.removeObject(forKey: UserDef.kCassettePath.rawValue) UserDefaults.current.removeObject(forKey: UserDef.kCassettePath.rawValue)
} }
return "" return ""
} }
@ -253,12 +253,12 @@ public extension LMMgr {
// MARK: - 使 // MARK: - 使
static func resetSpecifiedUserDataFolder() { static func resetSpecifiedUserDataFolder() {
UserDefaults.standard.set(dataFolderPath(isDefaultFolder: true), forKey: UserDef.kUserDataFolderSpecified.rawValue) UserDefaults.current.set(dataFolderPath(isDefaultFolder: true), forKey: UserDef.kUserDataFolderSpecified.rawValue)
Self.initUserLangModels() Self.initUserLangModels()
} }
static func resetCassettePath() { static func resetCassettePath() {
UserDefaults.standard.set("", forKey: UserDef.kCassettePath.rawValue) UserDefaults.current.set("", forKey: UserDef.kCassettePath.rawValue)
Self.loadCassetteData() Self.loadCassetteData()
} }

View File

@ -236,11 +236,11 @@ public class PrefMgr: PrefMgrProtocol {
// JIS // JIS
if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled { if chineseConversionEnabled, shiftJISShinjitaiOutputEnabled {
shiftJISShinjitaiOutputEnabled.toggle() shiftJISShinjitaiOutputEnabled.toggle()
UserDefaults.standard.set( UserDefaults.current.set(
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue
) )
} }
UserDefaults.standard.set( UserDefaults.current.set(
chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue
) )
} }
@ -252,11 +252,11 @@ public class PrefMgr: PrefMgrProtocol {
// JIS // JIS
if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled { if shiftJISShinjitaiOutputEnabled, chineseConversionEnabled {
chineseConversionEnabled.toggle() chineseConversionEnabled.toggle()
UserDefaults.standard.set( UserDefaults.current.set(
chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue chineseConversionEnabled, forKey: UserDef.kChineseConversionEnabled.rawValue
) )
} }
UserDefaults.standard.set( UserDefaults.current.set(
shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue shiftJISShinjitaiOutputEnabled, forKey: UserDef.kShiftJISShinjitaiOutputEnabled.rawValue
) )
} }

View File

@ -25,15 +25,15 @@ public extension PrefMgr {
showNotificationsWhenTogglingCapsLock = false showNotificationsWhenTogglingCapsLock = false
} }
if appleLanguages.isEmpty { if appleLanguages.isEmpty {
UserDefaults.standard.removeObject(forKey: UserDef.kAppleLanguages.rawValue) UserDefaults.current.removeObject(forKey: UserDef.kAppleLanguages.rawValue)
} }
// ( didSet ) // ( didSet )
candidateKeys = candidateKeys candidateKeys = candidateKeys
// //
if let clients = UserDefaults.standard.object( if let clients = UserDefaults.current.object(
forKey: UserDef.kClientsIMKTextInputIncapable.rawValue forKey: UserDef.kClientsIMKTextInputIncapable.rawValue
) as? [String] { ) as? [String] {
UserDefaults.standard.removeObject(forKey: UserDef.kClientsIMKTextInputIncapable.rawValue) UserDefaults.current.removeObject(forKey: UserDef.kClientsIMKTextInputIncapable.rawValue)
clients.forEach { neta in clients.forEach { neta in
guard !clientsIMKTextInputIncapable.keys.contains(neta) else { return } guard !clientsIMKTextInputIncapable.keys.contains(neta) else { return }
clientsIMKTextInputIncapable[neta] = true clientsIMKTextInputIncapable[neta] = true

View File

@ -2,6 +2,14 @@
import XCTest import XCTest
final class MainAssemblyTests: XCTestCase { final class MainAssemblyTests: XCTestCase {
func testExample() throws { override func setUpWithError() throws {
UserDefaults.unitTests = .init(suiteName: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
UserDefaults.pendingUnitTests = true
} }
override func tearDownWithError() throws {
UserDefaults.unitTests?.removeSuite(named: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
}
func testExample() throws {}
} }

View File

@ -16,7 +16,7 @@ import SwiftUI
import SwiftUIBackports import SwiftUIBackports
private let loc: String = private let loc: String =
(UserDefaults.standard.array(forKey: UserDef.kAppleLanguages.rawValue) as? [String] ?? ["auto"])[0] (UserDefaults.current.array(forKey: UserDef.kAppleLanguages.rawValue) as? [String] ?? ["auto"])[0]
@available(macOS 10.15, *) @available(macOS 10.15, *)
extension VwrPhraseEditorUI { extension VwrPhraseEditorUI {
@ -31,7 +31,7 @@ public struct VwrPhraseEditorUI: View {
) )
@Binding public var txtContent: String @Binding public var txtContent: String
@ObservedObject public var fileChangeIndicator = FileObserveProject.shared @ObservedObject public var fileChangeIndicator = FileObserveProject.shared
@State private var selAutoReloadExternalModifications: Bool = UserDefaults.standard.bool( @State private var selAutoReloadExternalModifications: Bool = UserDefaults.current.bool(
forKey: UserDef.kPhraseEditorAutoReloadExternalModifications.rawValue) forKey: UserDef.kPhraseEditorAutoReloadExternalModifications.rawValue)
@State var lblAddPhraseTag1 = PETerms.AddPhrases.locPhrase.localized.0 @State var lblAddPhraseTag1 = PETerms.AddPhrases.locPhrase.localized.0
@State var lblAddPhraseTag2 = PETerms.AddPhrases.locReadingOrStroke.localized.0 @State var lblAddPhraseTag2 = PETerms.AddPhrases.locReadingOrStroke.localized.0

View File

@ -0,0 +1,22 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import IMKUtils
// MARK: - IMKHelper Extension
public extension IMKHelper {
static var currentBasicKeyboardLayout: String {
UserDefaults.current.string(forKey: "BasicKeyboardLayout") ?? ""
}
static var isDynamicBasicKeyboardLayoutEnabled: Bool {
Self.arrDynamicBasicKeyLayouts.contains(currentBasicKeyboardLayout) || !currentBasicKeyboardLayout.isEmpty
}
}

View File

@ -98,7 +98,7 @@ public enum UserDef: String, CaseIterable {
public static func resetAll() { public static func resetAll() {
UserDef.allCases.forEach { UserDef.allCases.forEach {
UserDefaults.standard.removeObject(forKey: $0.rawValue) UserDefaults.current.removeObject(forKey: $0.rawValue)
} }
} }
@ -106,7 +106,7 @@ public enum UserDef: String, CaseIterable {
let data = snapshot.data let data = snapshot.data
guard !data.isEmpty else { return } guard !data.isEmpty else { return }
UserDef.allCases.forEach { UserDef.allCases.forEach {
UserDefaults.standard.set(data[$0.rawValue], forKey: $0.rawValue) UserDefaults.current.set(data[$0.rawValue], forKey: $0.rawValue)
} }
} }
@ -114,7 +114,7 @@ public enum UserDef: String, CaseIterable {
public var data: [String: Any] = [:] public var data: [String: Any] = [:]
public init() { public init() {
UserDef.allCases.forEach { UserDef.allCases.forEach {
data[$0.rawValue] = UserDefaults.standard.object(forKey: $0.rawValue) data[$0.rawValue] = UserDefaults.current.object(forKey: $0.rawValue)
} }
} }
} }
@ -310,7 +310,7 @@ public enum CandidateKey {
} }
public func vCLog(_ strPrint: StringLiteralType) { public func vCLog(_ strPrint: StringLiteralType) {
if UserDefaults.standard.bool(forKey: "_DebugMode") { if UserDefaults.current.bool(forKey: "_DebugMode") {
NSLog("vChewingDebug: %@", strPrint) NSLog("vChewingDebug: %@", strPrint)
} }
} }

View File

@ -119,6 +119,19 @@ public extension Bool {
} }
} }
// MARK: - User Defaults Storage
public extension UserDefaults {
//
static var pendingUnitTests = false
static var unitTests = UserDefaults(suiteName: "UnitTests")
static var current: UserDefaults {
pendingUnitTests ? .unitTests ?? .standard : .standard
}
}
// MARK: - Property Wrapper // MARK: - Property Wrapper
// Ref: https://www.avanderlee.com/swift/property-wrappers/ // Ref: https://www.avanderlee.com/swift/property-wrappers/
@ -127,7 +140,7 @@ public extension Bool {
public struct AppProperty<Value> { public struct AppProperty<Value> {
public let key: String public let key: String
public let defaultValue: Value public let defaultValue: Value
public var container: UserDefaults = .standard public var container: UserDefaults { .current }
public init(key: String, defaultValue: Value) { public init(key: String, defaultValue: Value) {
self.key = key self.key = key
self.defaultValue = defaultValue self.defaultValue = defaultValue