crossmate

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

NYTPuzzleUpgraderTests.swift (4833B)


      1 import Foundation
      2 import Testing
      3 
      4 @testable import Crossmate
      5 
      6 @Suite("NYTPuzzleUpgrader")
      7 struct NYTPuzzleUpgraderTests {
      8 
      9     // MARK: - Fixtures
     10 
     11     /// 3×3 open grid with default solutions ABC/DEF/GHI. Override `gridRows`
     12     /// for solution-diff tests; clue text overrides are independent so the
     13     /// verifier sees a clue-only change.
     14     private func openGridSource(
     15         clueAcross1: String = "Across one",
     16         clueAcross4: String = "Across four",
     17         clueAcross5: String = "Across five",
     18         clueDown1: String = "Down one",
     19         clueDown2: String = "Down two",
     20         clueDown3: String = "Down three",
     21         gridRows: [String] = ["ABC", "DEF", "GHI"]
     22     ) -> String {
     23         precondition(gridRows.count == 3 && gridRows.allSatisfy { $0.count == 3 })
     24         let metadata = """
     25         Title: Test
     26         Publisher: New York Times
     27         Date: 2025-01-26
     28         """
     29         let grid = gridRows.joined(separator: "\n")
     30         func col(_ idx: Int) -> String {
     31             String(gridRows.map { Array($0)[idx] })
     32         }
     33         let clues = """
     34         A1. \(clueAcross1) ~ \(gridRows[0])
     35         A4. \(clueAcross4) ~ \(gridRows[1])
     36         A5. \(clueAcross5) ~ \(gridRows[2])
     37 
     38         D1. \(clueDown1) ~ \(col(0))
     39         D2. \(clueDown2) ~ \(col(1))
     40         D3. \(clueDown3) ~ \(col(2))
     41         """
     42         return [metadata, grid, clues].joined(separator: "\n\n\n")
     43     }
     44 
     45     /// 2×2 open grid for the dimensions-diff test.
     46     private func smallGridSource() -> String {
     47         let metadata = """
     48         Title: Small
     49         Publisher: New York Times
     50         Date: 2025-01-26
     51         """
     52         let grid = "AB\nCD"
     53         let clues = """
     54         A1. Top ~ AB
     55         A3. Bottom ~ CD
     56 
     57         D1. Left ~ AC
     58         D2. Right ~ BD
     59         """
     60         return [metadata, grid, clues].joined(separator: "\n\n\n")
     61     }
     62 
     63     /// 3×3 grid with a single block at (0,2). Manually written because the
     64     /// open-grid helper assumes every cell is open.
     65     private func blockedSource() -> String {
     66         let metadata = """
     67         Title: Blocked
     68         Publisher: New York Times
     69         Date: 2025-01-26
     70         """
     71         let grid = "AB#\nDEF\nGHI"
     72         let clues = """
     73         A1. Top ~ AB
     74         A3. Middle ~ DEF
     75         A5. Bottom ~ GHI
     76 
     77         D1. Left ~ ADG
     78         D2. Mid ~ BEH
     79         D4. Right ~ FI
     80         """
     81         return [metadata, grid, clues].joined(separator: "\n\n\n")
     82     }
     83 
     84     // MARK: - Outcomes
     85 
     86     @Test("Clue-only diff is .upgraded — grid + solutions match")
     87     func clueOnlyDiffIsUpgraded() async {
     88         let old = openGridSource(clueAcross1: "[aria-label] Old prefix")
     89         let new = openGridSource(clueAcross1: "Old prefix")
     90         let outcome = await NYTPuzzleUpgrader.upgrade(
     91             date: Date(),
     92             currentSource: old,
     93             fetch: { _ in new }
     94         )
     95         guard case .upgraded(let returned) = outcome else {
     96             Issue.record("Expected .upgraded but got \(outcome)")
     97             return
     98         }
     99         #expect(returned == new)
    100     }
    101 
    102     @Test("Solution diff is .mismatched")
    103     func solutionDiffIsMismatched() async {
    104         let old = openGridSource()
    105         let new = openGridSource(gridRows: ["ABC", "DEF", "GHJ"])
    106         let outcome = await NYTPuzzleUpgrader.upgrade(
    107             date: Date(),
    108             currentSource: old,
    109             fetch: { _ in new }
    110         )
    111         if case .mismatched = outcome { return }
    112         Issue.record("Expected .mismatched but got \(outcome)")
    113     }
    114 
    115     @Test("Block-pattern diff is .mismatched")
    116     func blockPatternDiffIsMismatched() async {
    117         let old = openGridSource()
    118         let new = blockedSource()
    119         let outcome = await NYTPuzzleUpgrader.upgrade(
    120             date: Date(),
    121             currentSource: old,
    122             fetch: { _ in new }
    123         )
    124         if case .mismatched = outcome { return }
    125         Issue.record("Expected .mismatched but got \(outcome)")
    126     }
    127 
    128     @Test("Dimensions diff is .mismatched")
    129     func dimensionsDiffIsMismatched() async {
    130         let old = openGridSource()
    131         let new = smallGridSource()
    132         let outcome = await NYTPuzzleUpgrader.upgrade(
    133             date: Date(),
    134             currentSource: old,
    135             fetch: { _ in new }
    136         )
    137         if case .mismatched = outcome { return }
    138         Issue.record("Expected .mismatched but got \(outcome)")
    139     }
    140 
    141     @Test("Fetcher throwing is .failed")
    142     func fetcherThrowingIsFailed() async {
    143         struct StubError: Error {}
    144         let old = openGridSource()
    145         let outcome = await NYTPuzzleUpgrader.upgrade(
    146             date: Date(),
    147             currentSource: old,
    148             fetch: { _ in throw StubError() }
    149         )
    150         if case .failed = outcome { return }
    151         Issue.record("Expected .failed but got \(outcome)")
    152     }
    153 }