OpenPuzzleBannerTests.swift (2140B)
1 import Foundation 2 import Testing 3 4 @testable import Crossmate 5 6 /// Covers the open-time banner reconciler. The access-revoked banner is 7 /// otherwise posted only on the live revocation transition; a puzzle revoked 8 /// in an earlier process must still surface it when reopened. 9 @Suite("OpenPuzzleBanner") 10 struct OpenPuzzleBannerTests { 11 12 @Test("Opening a revoked game yields the sticky access-revoked banner") 13 func revokedGameYieldsBanner() throws { 14 let gameID = UUID() 15 let announcements = OpenPuzzleBanner.announcements( 16 for: OpenPuzzleState( 17 gameID: gameID, 18 isAccessRevoked: true, 19 isSyncSupported: true 20 ) 21 ) 22 #expect(announcements.count == 1) 23 let banner = try #require(announcements.first) 24 #expect(banner.id == "access-revoked-\(gameID.uuidString)") 25 #expect(banner.scope == .game(gameID)) 26 #expect(banner.severity == .error) 27 #expect(banner.dismissal == .sticky) 28 #expect(banner.blocksInput) 29 } 30 31 @Test("Opening a game with access intact yields no banners") 32 func intactGameYieldsNothing() { 33 let announcements = OpenPuzzleBanner.announcements( 34 for: OpenPuzzleState( 35 gameID: UUID(), 36 isAccessRevoked: false, 37 isSyncSupported: true 38 ) 39 ) 40 #expect(announcements.isEmpty) 41 } 42 43 @Test("Opening an unsupported sync version yields an input-blocking update banner") 44 func unsupportedSyncVersionYieldsBanner() throws { 45 let gameID = UUID() 46 let announcements = OpenPuzzleBanner.announcements( 47 for: OpenPuzzleState( 48 gameID: gameID, 49 isAccessRevoked: false, 50 isSyncSupported: false 51 ) 52 ) 53 #expect(announcements.count == 1) 54 let banner = try #require(announcements.first) 55 #expect(banner.id == "unsupported-sync-version-\(gameID.uuidString)") 56 #expect(banner.scope == .game(gameID)) 57 #expect(banner.dismissal == .sticky) 58 #expect(banner.blocksInput) 59 } 60 }