Files
OSGKeyboard/OSGKeyboardTests/KeyboardOnboardingOverlayTests.swift
T
Rocky 05e005e9ce feat: cursor navigation, key sounds, dictionary tooling, key security
Batch of in-progress app work from the working tree.

- feat(keyboard): CursorNavigation + CursorDragPad for caret movement;
  KeyboardSoundFeedback for system key click sounds
- feat(dictionary): DictionaryAliasGenerator + PersonalDictionaryEntrySheet;
  TranscriptPostProcessor quality gate; retire DictionaryLearner
- feat(ui): TabBarVisibility handling; drop PageHeaderRow /
  PageHeaderConfirmButton; refresh views and localizable strings
- fix(security): move the hardcoded DeepSeek key out of
  PreconfiguredKeys.swift into a gitignored PreconfiguredKeys.local.swift
  (seeded from .example by generate-xcodeproj.sh)
- docs(agents): add Conventional Commits versioning + bilingual changelog rules
- chore(gitignore): ignore PreconfiguredKeys.local.swift, .cache/, pycache

Custom language model / lexicon work stays on
feature/custom-language-model-asr. Changelog bullets added under
[Unreleased]; no version bump.
2026-07-05 18:27:23 +08:00

112 lines
4.0 KiB
Swift

// KeyboardOnboardingOverlayTests.swift
// OSGKeyboard · Tests
//
// v0.3.0: locks the AppGroupStore onboarding + app-context accessor
// wiring. These are the bytes the in-keyboard overlay reads every
// `viewWillAppear`, so a regression here breaks the first-launch UX
// silently (the overlay gets stuck on the welcome step, or the
// chip shows the wrong context).
import XCTest
@testable import OSGKeyboard
@testable import OSGKeyboardShared
final class KeyboardOnboardingOverlayTests: XCTestCase {
private var suiteName: String!
private var defaults: UserDefaults!
private var store: AppGroupStore!
override func setUp() {
super.setUp()
suiteName = "group.com.osgkeyboard.shared.tests.\(UUID().uuidString)"
defaults = UserDefaults(suiteName: suiteName)!
defaults.removePersistentDomain(forName: suiteName)
store = AppGroupStore(defaults: defaults)
}
override func tearDown() {
defaults.removePersistentDomain(forName: suiteName)
super.tearDown()
}
// MARK: - Onboarding flags
func testOnboardingFlagsDefaultFalseAndZero() {
XCTAssertFalse(store.hasCompletedOnboarding, "fresh install should not show as onboarded")
XCTAssertEqual(store.onboardingPage, 0, "fresh install should start at page 0")
}
func testOnboardingFlagsRoundTrip() {
store.onboardingPage = 3
store.hasCompletedOnboarding = true
XCTAssertEqual(store.onboardingPage, 3)
XCTAssertTrue(store.hasCompletedOnboarding)
}
func testOnboardingFlagsSurviveReconstruct() {
store.onboardingPage = 4
store.hasCompletedOnboarding = true
// Simulate the keyboard extension being torn down and rebuilt
// (which is what happens on every `viewDidLoad` cycle).
let store2 = AppGroupStore(defaults: defaults)
XCTAssertEqual(store2.onboardingPage, 4)
XCTAssertTrue(store2.hasCompletedOnboarding)
}
// MARK: - App context detection round-trip
func testDetectedAppContextRoundTrip() {
let now = Date()
store.setDetectedAppContext(.code, at: now)
let result = store.detectedAppContext
XCTAssertEqual(result?.context, .code)
XCTAssertEqual(result?.observedAt.timeIntervalSinceReferenceDate,
now.timeIntervalSinceReferenceDate,
accuracy: 0.001)
}
func testDetectedAppContextOverwrite() {
store.setDetectedAppContext(.code)
store.setDetectedAppContext(.email)
XCTAssertEqual(store.detectedAppContext?.context, .email,
"second setDetectedAppContext must overwrite the first")
}
func testDetectedAppContextEmptyBeforeSet() {
XCTAssertNil(store.detectedAppContext,
"detectedAppContext must be nil before any explicit set")
}
// MARK: - All cases enum surface
func testAllAppContextCasesHaveRawValue() {
// Locked: every case the LLM prompt knows about must be
// serializable through App Group UserDefaults. Adding a new
// case without a stable raw value silently breaks the cache.
for context in AppContext.allCases {
XCTAssertFalse(context.rawValue.isEmpty,
"AppContext.\(context) must have a non-empty rawValue")
}
}
// MARK: - Polish intensity default
func testPolishIntensityDefaultIsMedium() {
XCTAssertEqual(store.polishIntensity, .medium,
"default polish intensity should match Typeless baseline")
}
func testPolishIntensityRoundTrip() {
store.setPolishIntensity(.heavy)
XCTAssertEqual(store.polishIntensity, .heavy)
store.setPolishIntensity(.light)
XCTAssertEqual(store.polishIntensity, .light)
}
func testPolishIntensityLegacyOffMigratesToMedium() {
defaults.set(PolishIntensity.legacyOffRawValue, forKey: "config.polishIntensity")
XCTAssertEqual(store.polishIntensity, .medium)
}
}