RevealConfirmationTests.swift (4575B)
1 import Foundation 2 import Testing 3 4 @testable import Crossmate 5 6 /// Cancel/confirm behaviour of the reveal confirmation that every reveal 7 /// surface routes through — the toolbar Hints menu, the app-level ⌘R 8 /// commands, and the VoiceOver cell action: requesting a reveal must never 9 /// touch the grid by itself, and only an explicit confirmation applies it. 10 @MainActor 11 @Suite("RevealConfirmation") 12 struct RevealConfirmationTests { 13 14 /// 3x3 grid with a centre block: 15 /// A B C 16 /// D # E 17 /// F G H 18 private func makeSession() throws -> PlayerSession { 19 let source = """ 20 Title: Reveal Confirmation Fixture 21 22 23 ABC 24 D#E 25 FGH 26 27 28 A1. Top row ~ ABC 29 A3. Bottom row ~ FGH 30 D1. Left column ~ ADF 31 D2. Right column ~ CEH 32 """ 33 let puzzle = Puzzle(xd: try XD.parse(source)) 34 let game = Game(puzzle: puzzle) 35 let mutator = GameMutator(game: game, gameID: UUID(), movesUpdater: nil) 36 return PlayerSession(game: game, mutator: mutator) 37 } 38 39 private func isUntouched(_ session: PlayerSession) -> Bool { 40 session.game.squares.allSatisfy { row in row.allSatisfy { $0.entry.isEmpty } } 41 } 42 43 @Test("Requesting a reveal arms the confirmation without touching the grid") 44 func requestArmsWithoutRevealing() throws { 45 let session = try makeSession() 46 let confirmation = RevealConfirmation() 47 48 confirmation.request(.square) 49 50 #expect(confirmation.isConfirming) 51 #expect(confirmation.pendingScope == .square) 52 #expect(isUntouched(session)) 53 } 54 55 @Test("Cancelling a requested reveal leaves the grid untouched") 56 func cancelLeavesGridUntouched() throws { 57 let session = try makeSession() 58 let confirmation = RevealConfirmation() 59 60 confirmation.request(.puzzle) 61 // The alert's Cancel button only dismisses; nothing performs. 62 confirmation.isConfirming = false 63 64 #expect(isUntouched(session)) 65 #expect(session.game.completionState == .incomplete) 66 } 67 68 @Test("Confirming a square reveal fills the selected square and announces it") 69 func confirmRevealsSquare() throws { 70 let session = try makeSession() 71 let confirmation = RevealConfirmation() 72 session.select(row: 0, col: 0) 73 74 confirmation.request(.square) 75 let announcement = confirmation.performPending(on: session) 76 77 #expect(session.game.squares[0][0].entry == "A") 78 #expect(session.game.squares[0][0].mark.isRevealed) 79 #expect(announcement == "Revealed A") 80 // Only the confirmed square was spent, not its word. 81 #expect(session.game.squares[0][1].entry.isEmpty) 82 } 83 84 @Test("Confirming after a cancelled request applies the new scope, not the stale one") 85 func reRequestAfterCancelUsesNewScope() throws { 86 let session = try makeSession() 87 let confirmation = RevealConfirmation() 88 session.select(row: 0, col: 0) 89 90 confirmation.request(.puzzle) 91 confirmation.isConfirming = false 92 confirmation.request(.square) 93 confirmation.performPending(on: session) 94 95 #expect(session.game.squares[0][0].entry == "A") 96 #expect(session.game.squares[2][2].entry.isEmpty) 97 #expect(session.game.completionState == .incomplete) 98 } 99 100 @Test("Confirming a word reveal fills the current word without a square announcement") 101 func confirmRevealsWord() throws { 102 let session = try makeSession() 103 let confirmation = RevealConfirmation() 104 session.select(row: 0, col: 1) 105 106 confirmation.request(.word) 107 let announcement = confirmation.performPending(on: session) 108 109 #expect(session.game.squares[0][0].entry == "A") 110 #expect(session.game.squares[0][1].entry == "B") 111 #expect(session.game.squares[0][2].entry == "C") 112 #expect(session.game.squares[2][0].entry.isEmpty) 113 #expect(announcement == nil) 114 } 115 116 @Test("Confirming a puzzle reveal fills the grid and latches completion") 117 func confirmRevealsPuzzle() throws { 118 let session = try makeSession() 119 let confirmation = RevealConfirmation() 120 121 confirmation.request(.puzzle) 122 let announcement = confirmation.performPending(on: session) 123 124 #expect(session.game.squares[0][0].entry == "A") 125 #expect(session.game.squares[2][2].entry == "H") 126 #expect(session.game.squares[2][2].mark.isRevealed) 127 #expect(session.game.completionState == .solved) 128 #expect(announcement == nil) 129 } 130 }