crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

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 }