~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
61 lines · 2.4 KB Raw History
@testable import Grain
import SwiftUI
import XCTest

/// The animation a double-tap plays over a photo — a heart, three ripples and a
/// scatter of particles, each on its own curve and delay.
@MainActor
final class LikeBurstRenderTests: GrainTestCase {
    private var account: TestAccount!

    /// Async overrides: the synchronous `setUp`/`tearDown` are nonisolated, so
    /// touching main-actor state from them warns.
    override func setUp() async throws {
        try await super.setUp()
        account = TestAccount()
        MockURLProtocol.respondByPath(Fixtures.routes)
    }

    override func tearDown() async throws {
        MockURLProtocol.handler = nil
        account.restore()
        try await super.tearDown()
    }

    /// The burst that plays when a card is double-tapped: a heart plus three
    /// ripples, each on its own curve and delay.
    func testRendersTheDoubleTapHeartThroughItsBurst() {
        let state = HeartAnimationState(position: CGPoint(x: 120, y: 200))
        ViewRender.render(DoubleTapHeartView(state: state), settle: 0)

        state.start()
        ViewRender.render(DoubleTapHeartView(state: state), settle: 0.2)

        // The burst runs on transforms and opacity, which don't move any
        // frames — so wait on the state rather than on the rendered tree.
        let deadline = Date().addingTimeInterval(5)
        while !state.isComplete, Date() < deadline {
            RunLoop.current.run(until: Date().addingTimeInterval(0.05))
        }
        XCTAssertTrue(state.isComplete, "The heart never finished, so it would stay on the card")

        // Finished, and the view stops drawing rather than lingering.
        ViewRender.render(DoubleTapHeartView(state: state), settle: 0)
    }

    func testTheHeartIsGivenARandomTilt() {
        let tilts = (0 ..< 12).map { _ in HeartAnimationState(position: .zero).rotation }

        XCTAssertTrue(tilts.allSatisfy { $0 >= -20 && $0 <= 20 })
        XCTAssertGreaterThan(Set(tilts).count, 1, "Every heart landing at the same angle would read as a glitch")
    }

    /// Each particle in the burst sits at its own offset, so the slots have to
    /// be distinct — and the view indexes a fixed table, so every slot the card
    /// can ask for has to exist.
    func testRendersEveryLikeParticleSlot() {
        for index in 0 ..< 5 {
            ViewRender.render(LikeParticleView(index: index), settle: 0)
        }
    }
}