31f5937a7f
Reduce extension memory pressure and delivery races while adding richer candidates, tactile feedback, and safer two-level creative polishing.
191 lines
8.2 KiB
Swift
191 lines
8.2 KiB
Swift
// AppGroupConfigurationTests.swift
|
|
// OSGKeyboardTests
|
|
|
|
import XCTest
|
|
@testable import OSGKeyboardShared
|
|
|
|
final class AppGroupConfigurationTests: XCTestCase {
|
|
|
|
private func makeDefaults() -> UserDefaults {
|
|
let suite = "group.com.osgkeyboard.shared.tests.config.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defaults.removePersistentDomain(forName: suite)
|
|
return defaults
|
|
}
|
|
|
|
func testLoadDefaultsWhenSuiteIsEmpty() {
|
|
let defaults = makeDefaults()
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
|
|
XCTAssertEqual(config.providerId, "deepseek")
|
|
XCTAssertEqual(config.asrProviderId, "volcengine")
|
|
XCTAssertEqual(config.modeId, "polish")
|
|
XCTAssertEqual(config.localeId, "auto")
|
|
// Privacy-critical: the default engine must keep audio on-device.
|
|
XCTAssertEqual(config.engineMode, "local")
|
|
XCTAssertFalse(config.hasCompletedOnboarding)
|
|
XCTAssertEqual(config.onboardingPage, 0)
|
|
XCTAssertFalse(config.hasAcknowledgedCloudSharing)
|
|
XCTAssertEqual(config.translationTargetLocaleId, TranslationLanguageCatalog.offLocaleId)
|
|
XCTAssertFalse(config.translationEnabled)
|
|
XCTAssertEqual(config.handednessPreference, .left)
|
|
XCTAssertTrue(config.cursorDragNavigationEnabled)
|
|
XCTAssertEqual(config.keyboardHapticIntensity, .light)
|
|
XCTAssertEqual(config.polishIntensity, .light)
|
|
XCTAssertTrue(config.personalDictionary.entries.isEmpty)
|
|
XCTAssertTrue(config.flowSkipAppSwitch)
|
|
XCTAssertEqual(config.flowKeepAliveMode, .pictureInPicture)
|
|
XCTAssertEqual(config.flowInactivityDuration, .fiveMinutes)
|
|
}
|
|
|
|
func testLoadPreservesStoredLiveActivityKeepAliveMode() {
|
|
let defaults = makeDefaults()
|
|
defaults.set(
|
|
FlowKeepAliveMode.liveActivity.rawValue,
|
|
forKey: AppGroupConfiguration.Keys.flowKeepAliveMode
|
|
)
|
|
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
|
|
XCTAssertEqual(config.flowKeepAliveMode, .liveActivity)
|
|
}
|
|
|
|
func testSaveAndLoadRoundTrip() {
|
|
let defaults = makeDefaults()
|
|
var config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
config.providerId = "anthropic"
|
|
config.baseURL = "https://example.com/v1"
|
|
config.model = "claude-test"
|
|
config.asrProviderId = "zhipu"
|
|
config.asrBaseURL = "https://open.bigmodel.cn/api/paas/v4"
|
|
config.asrModel = "glm-asr-2512"
|
|
config.modeId = "polish"
|
|
config.localeId = "zh-Hans"
|
|
// Non-default value so the round-trip proves persistence.
|
|
config.engineMode = "cloud"
|
|
config.hasCompletedOnboarding = true
|
|
config.onboardingPage = 2
|
|
config.hasAcknowledgedCloudSharing = true
|
|
config.uiLanguage = .chinese
|
|
config.translationTargetLocaleId = "en"
|
|
config.handednessPreference = .right
|
|
config.cursorDragNavigationEnabled = false
|
|
config.keyboardHapticIntensity = .strong
|
|
config.polishIntensity = .heavy
|
|
config.flowSkipAppSwitch = false
|
|
config.flowKeepAliveMode = .pictureInPicture
|
|
// Use a non-default value so the round-trip actually proves persistence.
|
|
config.flowInactivityDuration = .threeHours
|
|
config.save(to: defaults)
|
|
|
|
let loaded = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
XCTAssertEqual(loaded.providerId, "anthropic")
|
|
XCTAssertEqual(loaded.baseURL, "https://example.com/v1")
|
|
XCTAssertEqual(loaded.model, "claude-test")
|
|
XCTAssertEqual(loaded.asrProviderId, "zhipu")
|
|
XCTAssertEqual(loaded.asrBaseURL, "https://open.bigmodel.cn/api/paas/v4")
|
|
XCTAssertEqual(loaded.asrModel, "glm-asr-2512")
|
|
XCTAssertEqual(loaded.localeId, "zh-Hans")
|
|
XCTAssertEqual(loaded.engineMode, "cloud")
|
|
XCTAssertTrue(loaded.hasCompletedOnboarding)
|
|
XCTAssertEqual(loaded.onboardingPage, 2)
|
|
XCTAssertTrue(loaded.hasAcknowledgedCloudSharing)
|
|
XCTAssertEqual(loaded.uiLanguage, .chinese)
|
|
XCTAssertEqual(loaded.translationTargetLocaleId, "en")
|
|
XCTAssertTrue(loaded.translationEnabled)
|
|
XCTAssertEqual(loaded.handednessPreference, .right)
|
|
XCTAssertFalse(loaded.cursorDragNavigationEnabled)
|
|
XCTAssertEqual(loaded.keyboardHapticIntensity, .strong)
|
|
XCTAssertEqual(loaded.polishIntensity, .heavy)
|
|
XCTAssertFalse(loaded.flowSkipAppSwitch)
|
|
XCTAssertEqual(loaded.flowKeepAliveMode, .pictureInPicture)
|
|
XCTAssertEqual(loaded.flowInactivityDuration, .threeHours)
|
|
}
|
|
|
|
func testRetiredMediumPolishIntensityMigratesToLight() {
|
|
let defaults = makeDefaults()
|
|
defaults.set("medium", forKey: AppGroupConfiguration.Keys.polishIntensity)
|
|
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
|
|
XCTAssertEqual(config.polishIntensity, .light)
|
|
}
|
|
|
|
/// Existing installs retain their legacy engine, but an unset inactivity
|
|
/// duration adopts the current privacy-safe default.
|
|
func testDefaultMigrationUsesPrivacySafeInactivityDuration() {
|
|
let defaults = makeDefaults()
|
|
defaults.set(true, forKey: AppGroupConfiguration.Keys.hasCompletedOnboarding)
|
|
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
|
|
XCTAssertEqual(config.engineMode, "cloud", "pre-picker installs stay on their old default")
|
|
XCTAssertEqual(config.flowInactivityDuration, .fiveMinutes)
|
|
// The resolution is persisted so it is stable and sync-invisible.
|
|
XCTAssertEqual(defaults.string(forKey: AppGroupConfiguration.Keys.engineMode), "cloud")
|
|
XCTAssertEqual(
|
|
defaults.string(forKey: AppGroupConfiguration.Keys.flowInactivityDuration),
|
|
FlowInactivityDuration.fiveMinutes.rawValue
|
|
)
|
|
}
|
|
|
|
func testPreviousDefaultInactivityIsMigratedToFiveMinutesOnce() {
|
|
let defaults = makeDefaults()
|
|
defaults.set(FlowInactivityDuration.thirtyMinutes.rawValue,
|
|
forKey: AppGroupConfiguration.Keys.flowInactivityDuration)
|
|
|
|
let first = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
XCTAssertEqual(first.flowInactivityDuration, .fiveMinutes)
|
|
XCTAssertTrue(defaults.bool(
|
|
forKey: AppGroupConfiguration.Keys.flowInactivityMigratedToFiveMinuteDefault
|
|
))
|
|
|
|
// After migration, an explicit 30-minute choice sticks.
|
|
var updated = first
|
|
updated.flowInactivityDuration = .thirtyMinutes
|
|
updated.save(to: defaults)
|
|
let second = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
XCTAssertEqual(second.flowInactivityDuration, .thirtyMinutes)
|
|
}
|
|
|
|
func testDefaultMigrationGivesFreshInstallPrivacyDefaults() {
|
|
let defaults = makeDefaults()
|
|
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
|
|
XCTAssertEqual(config.engineMode, "local")
|
|
XCTAssertEqual(config.flowInactivityDuration, .fiveMinutes)
|
|
XCTAssertEqual(defaults.string(forKey: AppGroupConfiguration.Keys.engineMode), "local")
|
|
}
|
|
|
|
func testTranslationEnabledDerivedFromTargetLocale() {
|
|
let defaults = makeDefaults()
|
|
var config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
XCTAssertFalse(config.translationEnabled)
|
|
|
|
config.translationTargetLocaleId = "ja"
|
|
XCTAssertTrue(config.translationEnabled)
|
|
|
|
config.translationTargetLocaleId = TranslationLanguageCatalog.offLocaleId
|
|
XCTAssertFalse(config.translationEnabled)
|
|
}
|
|
|
|
func testCloudDeepSeekProviderIsPreserved() {
|
|
let defaults = makeDefaults()
|
|
defaults.set("deepseek", forKey: AppGroupConfiguration.Keys.providerId)
|
|
defaults.set("cloud", forKey: AppGroupConfiguration.Keys.engineMode)
|
|
|
|
let config = AppGroupConfiguration.load(fromAvailable: defaults)
|
|
XCTAssertEqual(config.providerId, "deepseek")
|
|
XCTAssertEqual(defaults.string(forKey: AppGroupConfiguration.Keys.providerId), "deepseek")
|
|
}
|
|
|
|
func testLoadFromNilUsesAppGroupWhenAvailable() {
|
|
if AppGroup.defaultsIfAvailable != nil {
|
|
XCTAssertNotNil(AppGroupConfiguration.load(from: nil))
|
|
} else {
|
|
XCTAssertNil(AppGroupConfiguration.load(from: nil))
|
|
}
|
|
}
|
|
}
|