perf(keyboard): track KeyboardState per property with @Observable
- KeyboardState moves from ObservableObject/@Published to @Observable, so a view re-renders only for the properties it reads. Previously unpublished state and injected action hooks are @ObservationIgnored. - KeyboardViewController replaces the $phase/$surface Combine sinks with Observations loops tied to the same cancellables. - AIKeyboardView reads the 200 ms mic level and live ASR partials in small child views (AssistantWaveform, LiveTranscriptReader), so those ticks no longer rebuild the whole assistant surface. - Tests observe changes with withObservationTracking instead of Combine publishers.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
// Verifies that the transient suggestion belongs to one pasteboard generation
|
||||
// and one keyboard presentation; clipboard history remains independently usable.
|
||||
|
||||
import Combine
|
||||
import Observation
|
||||
@testable import OSGKeyboardShared
|
||||
import XCTest
|
||||
|
||||
@@ -136,17 +136,19 @@ final class ClipboardSuggestionLifecycleTests: XCTestCase {
|
||||
func testUnchangedPollingDoesNotRepublishSuggestion() {
|
||||
copy("stable clipboard")
|
||||
coordinator.captureIfNeeded()
|
||||
var publications = 0
|
||||
let cancellable = state.$clipboardSuggestionText
|
||||
.dropFirst()
|
||||
.sink { _ in publications += 1 }
|
||||
defer { cancellable.cancel() }
|
||||
let republished = expectation(description: "suggestion republished")
|
||||
republished.isInverted = true
|
||||
withObservationTracking {
|
||||
_ = state.clipboardSuggestionText
|
||||
} onChange: {
|
||||
republished.fulfill()
|
||||
}
|
||||
|
||||
coordinator.captureIfNeeded()
|
||||
coordinator.captureIfNeeded()
|
||||
coordinator.captureIfNeeded()
|
||||
|
||||
XCTAssertEqual(publications, 0)
|
||||
wait(for: [republished], timeout: 0.1)
|
||||
XCTAssertEqual(pasteboard.stringReadCount, 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// *don't* attempt that here — the State class is what we care about
|
||||
// for correctness.
|
||||
|
||||
import Combine
|
||||
import Observation
|
||||
@testable import OSGKeyboardShared
|
||||
import XCTest
|
||||
|
||||
@@ -189,19 +189,17 @@ final class KeyboardStateTests: XCTestCase {
|
||||
)
|
||||
state.enabledClipboardSkills = [first]
|
||||
let published = expectation(description: "resolved skill snapshot published")
|
||||
var cancellable: AnyCancellable?
|
||||
cancellable = state.$enabledClipboardSkills
|
||||
.dropFirst()
|
||||
.sink { skills in
|
||||
XCTAssertEqual(skills.first?.customPrompt, "Second prompt")
|
||||
published.fulfill()
|
||||
}
|
||||
withObservationTracking {
|
||||
_ = state.enabledClipboardSkills
|
||||
} onChange: {
|
||||
published.fulfill()
|
||||
}
|
||||
|
||||
state.enabledClipboardSkills = [second]
|
||||
|
||||
wait(for: [published], timeout: 1)
|
||||
XCTAssertEqual(state.enabledClipboardSkills.first?.customPrompt, "Second prompt")
|
||||
XCTAssertEqual(state.enabledClipboardSkillIDs, ["official.rewrite"])
|
||||
withExtendedLifetime(cancellable) {}
|
||||
}
|
||||
|
||||
func testReturnKeyRoleActionFillAndTitles() {
|
||||
|
||||
Reference in New Issue
Block a user