Files
tvbox-Swift/tvboxTests/PlayerGestureLayerZoomClampingTests.swift
T
2026-05-14 17:50:06 +08:00

131 lines
6.3 KiB
Swift

#if os(iOS)
import Testing
import CoreGraphics
@testable import TVBox
/// Property 6: Pinch zoom clamping invariant
/// For any scale s > 0, clampZoom returns a value in [1.0, 3.0]
///
/// **Validates: Requirements 5.2, 5.3**
@Suite("Property 6: Pinch zoom clamping invariant")
struct PlayerGestureLayerZoomClampingTests {
// MARK: - Property Test: Default bounds [1.0, 3.0]
/// Property: For any positive scale, clampZoom with default bounds always returns a value in [1.0, 3.0]
@Test("clampZoom clamps to [1.0, 3.0] for various positive scales", arguments: [
// Boundary values
CGFloat(0.001), // very small positive
CGFloat(0.5), // below min
CGFloat(0.99), // just below min
CGFloat(1.0), // exactly min
CGFloat(1.001), // just above min
CGFloat(1.5), // mid-range
CGFloat(2.0), // mid-range
CGFloat(2.5), // mid-range
CGFloat(2.99), // just below max
CGFloat(3.0), // exactly max
CGFloat(3.001), // just above max
CGFloat(3.5), // above max
CGFloat(5.0), // well above max
CGFloat(10.0), // far above max
CGFloat(100.0), // extreme above max
CGFloat(1000.0), // very extreme
CGFloat(0.0001), // near-zero positive
])
func clampZoomDefaultBounds(scale: CGFloat) {
let result = PlayerGestureLayer.clampZoom(scale: scale)
#expect(result >= 1.0, "clampZoom(\(scale)) = \(result) should be >= 1.0")
#expect(result <= 3.0, "clampZoom(\(scale)) = \(result) should be <= 3.0")
}
// MARK: - Property Test: Custom bounds
/// Property: For any positive scale and valid custom bounds, clampZoom returns a value in [minZoom, maxZoom]
@Test("clampZoom respects custom min/max bounds", arguments: [
(CGFloat(0.5), CGFloat(0.5), CGFloat(2.0)), // scale below custom min
(CGFloat(1.0), CGFloat(0.5), CGFloat(2.0)), // scale within custom range
(CGFloat(2.5), CGFloat(0.5), CGFloat(2.0)), // scale above custom max
(CGFloat(0.1), CGFloat(0.2), CGFloat(5.0)), // scale below wide range min
(CGFloat(3.0), CGFloat(0.2), CGFloat(5.0)), // scale within wide range
(CGFloat(6.0), CGFloat(0.2), CGFloat(5.0)), // scale above wide range max
(CGFloat(2.0), CGFloat(2.0), CGFloat(2.0)), // min equals max
(CGFloat(1.0), CGFloat(2.0), CGFloat(2.0)), // below when min equals max
(CGFloat(3.0), CGFloat(2.0), CGFloat(2.0)), // above when min equals max
(CGFloat(0.01), CGFloat(0.5), CGFloat(10.0)), // very small scale, wide range
(CGFloat(100.0), CGFloat(0.5), CGFloat(10.0)), // very large scale, wide range
])
func clampZoomCustomBounds(scale: CGFloat, minZoom: CGFloat, maxZoom: CGFloat) {
let result = PlayerGestureLayer.clampZoom(scale: scale, minZoom: minZoom, maxZoom: maxZoom)
#expect(result >= minZoom, "clampZoom(\(scale), min: \(minZoom), max: \(maxZoom)) = \(result) should be >= \(minZoom)")
#expect(result <= maxZoom, "clampZoom(\(scale), min: \(minZoom), max: \(maxZoom)) = \(result) should be <= \(maxZoom)")
}
// MARK: - Property Test: Idempotency
/// Property: Clamping an already-clamped value should return the same value
@Test("clampZoom is idempotent - clamping twice gives same result", arguments: [
CGFloat(0.5),
CGFloat(1.0),
CGFloat(2.0),
CGFloat(3.0),
CGFloat(5.0),
])
func clampZoomIdempotent(scale: CGFloat) {
let firstClamp = PlayerGestureLayer.clampZoom(scale: scale)
let secondClamp = PlayerGestureLayer.clampZoom(scale: firstClamp)
#expect(firstClamp == secondClamp, "clampZoom should be idempotent: clampZoom(clampZoom(\(scale))) = \(secondClamp) != clampZoom(\(scale)) = \(firstClamp)")
}
// MARK: - Property Test: Values within range are unchanged
/// Property: If scale is already within [minZoom, maxZoom], it should be returned unchanged
@Test("clampZoom preserves values already within bounds", arguments: [
CGFloat(1.0),
CGFloat(1.5),
CGFloat(2.0),
CGFloat(2.5),
CGFloat(3.0),
])
func clampZoomPreservesValidValues(scale: CGFloat) {
let result = PlayerGestureLayer.clampZoom(scale: scale)
#expect(result == scale, "clampZoom(\(scale)) should return \(scale) unchanged, got \(result)")
}
// MARK: - Randomized Property Test
/// Property: Randomized test with many generated values to approximate property-based testing
@Test("clampZoom satisfies clamping invariant for generated positive scales")
func clampZoomRandomizedProperty() {
// Generate a wide range of positive scale values to test the property
var rng = SystemRandomNumberGenerator()
for _ in 0..<200 {
// Generate random positive CGFloat values across different magnitudes
let magnitude = CGFloat.random(in: 0..<4, using: &rng) // 0 to 4 for exponent
let scale = CGFloat(pow(10.0, Double(magnitude))) * CGFloat.random(in: 0.001..<1.0, using: &rng)
let result = PlayerGestureLayer.clampZoom(scale: scale)
#expect(result >= 1.0, "Random scale \(scale): result \(result) should be >= 1.0")
#expect(result <= 3.0, "Random scale \(scale): result \(result) should be <= 3.0")
}
}
/// Property: Randomized test with custom bounds
@Test("clampZoom satisfies clamping invariant for generated scales with custom bounds")
func clampZoomRandomizedCustomBounds() {
var rng = SystemRandomNumberGenerator()
for _ in 0..<200 {
// Generate random bounds where minZoom <= maxZoom
let minZoom = CGFloat.random(in: 0.1..<5.0, using: &rng)
let maxZoom = minZoom + CGFloat.random(in: 0.0..<10.0, using: &rng)
let scale = CGFloat.random(in: 0.001..<20.0, using: &rng)
let result = PlayerGestureLayer.clampZoom(scale: scale, minZoom: minZoom, maxZoom: maxZoom)
#expect(result >= minZoom, "scale=\(scale), min=\(minZoom), max=\(maxZoom): result \(result) should be >= \(minZoom)")
#expect(result <= maxZoom, "scale=\(scale), min=\(minZoom), max=\(maxZoom): result \(result) should be <= \(maxZoom)")
}
}
}
#endif