Repo // Make UserDefaults unit-testable.
This commit is contained in:
parent
c5c99894a9
commit
3a8060bf88
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue