mirror of
https://github.com/phranck/TUIkit.git
synced 2026-05-21 09:50:35 +00:00
5ad97132b8
- Replace custom Observable protocol and @Published with Apple's @Observable macro - Add withObservationTracking in renderToBuffer for automatic per-property dependency tracking - Add type-based @Environment(Type.self) and .environment(object) for observable objects - Add ObjectEnvironmentModifier for injecting observable objects into the environment - Add needsCacheClear flag to AppState for thread-safe cache invalidation - Add cross-platform test script (scripts/test-linux.sh) for Docker-based Linux verification - Add DemoAppHeader with system info display (OS, version, architecture) - Consolidate Example App: extract ImageDemoHelpers, KeyboardHelpSection, ValueDisplayRow - Add pre-push verification rule to CLAUDE.md - Verified on both macOS and Linux (swift:6.0 container), 1155 tests passing
121 lines
3.2 KiB
Swift
121 lines
3.2 KiB
Swift
// 🖥️ TUIKit — Terminal UI Kit for Swift
|
||
// ObservationTrackingTests.swift
|
||
//
|
||
// Created by LAYERED.work
|
||
// License: MIT
|
||
|
||
import Observation
|
||
import Testing
|
||
|
||
@testable import TUIkit
|
||
|
||
// MARK: - Test Observable
|
||
|
||
@Observable
|
||
private class TestModel {
|
||
var count = 0
|
||
var name = "test"
|
||
|
||
init() {}
|
||
}
|
||
|
||
// MARK: - Tests
|
||
|
||
@MainActor
|
||
@Suite("Observation Tracking Tests")
|
||
struct ObservationTrackingTests {
|
||
|
||
@Test("@Observable property change triggers render via withObservationTracking")
|
||
func observationTriggersRender() {
|
||
let model = TestModel()
|
||
AppState.shared.didRender()
|
||
_ = AppState.shared.consumeNeedsCacheClear()
|
||
#expect(!AppState.shared.needsRender)
|
||
|
||
withObservationTracking {
|
||
_ = model.count
|
||
} onChange: {
|
||
AppState.shared.setNeedsRenderWithCacheClear()
|
||
}
|
||
|
||
model.count = 42
|
||
#expect(AppState.shared.needsRender)
|
||
#expect(AppState.shared.consumeNeedsCacheClear())
|
||
|
||
AppState.shared.didRender()
|
||
}
|
||
|
||
@Test("Multiple @Observable properties tracked independently")
|
||
func multiplePropertiesTracked() {
|
||
let model = TestModel()
|
||
AppState.shared.didRender()
|
||
_ = AppState.shared.consumeNeedsCacheClear()
|
||
|
||
// Track count property
|
||
withObservationTracking {
|
||
_ = model.count
|
||
} onChange: {
|
||
AppState.shared.setNeedsRenderWithCacheClear()
|
||
}
|
||
|
||
model.count = 10
|
||
#expect(AppState.shared.needsRender)
|
||
#expect(AppState.shared.consumeNeedsCacheClear())
|
||
AppState.shared.didRender()
|
||
|
||
// Track name property
|
||
withObservationTracking {
|
||
_ = model.name
|
||
} onChange: {
|
||
AppState.shared.setNeedsRenderWithCacheClear()
|
||
}
|
||
|
||
model.name = "changed"
|
||
#expect(AppState.shared.needsRender)
|
||
#expect(AppState.shared.consumeNeedsCacheClear())
|
||
AppState.shared.didRender()
|
||
}
|
||
|
||
@Test("onChange fires only once per tracking registration")
|
||
func onChangeFiresOnce() {
|
||
let model = TestModel()
|
||
AppState.shared.didRender()
|
||
_ = AppState.shared.consumeNeedsCacheClear()
|
||
|
||
withObservationTracking {
|
||
_ = model.count
|
||
} onChange: {
|
||
AppState.shared.setNeedsRenderWithCacheClear()
|
||
}
|
||
|
||
model.count = 1
|
||
#expect(AppState.shared.needsRender)
|
||
AppState.shared.didRender()
|
||
_ = AppState.shared.consumeNeedsCacheClear()
|
||
|
||
// Second mutation without re-registering should NOT trigger
|
||
model.count = 2
|
||
#expect(!AppState.shared.needsRender)
|
||
}
|
||
|
||
@Test("Untracked property change does not trigger render")
|
||
func untrackedPropertyNoRender() {
|
||
let model = TestModel()
|
||
AppState.shared.didRender()
|
||
_ = AppState.shared.consumeNeedsCacheClear()
|
||
|
||
// Only track count, not name
|
||
withObservationTracking {
|
||
_ = model.count
|
||
} onChange: {
|
||
AppState.shared.setNeedsRenderWithCacheClear()
|
||
}
|
||
|
||
// Changing name (untracked) should not trigger
|
||
model.name = "changed"
|
||
#expect(!AppState.shared.needsRender)
|
||
|
||
AppState.shared.didRender()
|
||
}
|
||
}
|