Complete native rewrite of the web-based SoliCards game as a SwiftUI multiplatform app targeting iOS 17+, iPadOS 17+, and macOS 14+. Three solitaire variants (Klondike, Spider, FreeCell) with full game rules, drag & drop, smart zoom layout, 6 themes, 4 difficulty levels, SwiftData persistence, VoiceOver accessibility, and 57 unit tests. Key features: - MVVM + Protocol-Oriented Strategy architecture - DragGesture with coordinate-space hit-testing (long press + drag) - Smart zoom: cards auto-size to fit screen based on deepest column - Landscape: 30% bigger cards with scrollable overflow (iOS) - macOS: 120pt card cap, 92% height buffer for window resizing - Auto-save, game resume, statistics tracking via SwiftData - Privacy manifest, app icon, String Catalog, zero dependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
82 lines
2.6 KiB
Swift
82 lines
2.6 KiB
Swift
import Testing
|
|
@testable import SoliCards
|
|
|
|
@Suite("AutoCompleter Tests")
|
|
struct AutoCompleterTests {
|
|
let rules = KlondikeRules()
|
|
|
|
@Test("Finds ace to move to empty foundation")
|
|
func findAceMove() {
|
|
let ace = Card(suit: .hearts, rank: .ace, isFaceUp: true)
|
|
var snapshot = emptySnapshot()
|
|
snapshot.tableaus[0] = [ace]
|
|
|
|
let result = AutoCompleter.findNextAutoMove(state: snapshot, rules: rules)
|
|
#expect(result != nil)
|
|
#expect(result?.to == .foundation(0))
|
|
}
|
|
|
|
@Test("Finds card to build on foundation")
|
|
func findFoundationBuild() {
|
|
let ace = Card(suit: .hearts, rank: .ace, isFaceUp: true)
|
|
let two = Card(suit: .hearts, rank: .two, isFaceUp: true)
|
|
var snapshot = emptySnapshot()
|
|
snapshot.foundations[0] = [ace]
|
|
snapshot.tableaus[0] = [two]
|
|
|
|
let result = AutoCompleter.findNextAutoMove(state: snapshot, rules: rules)
|
|
#expect(result != nil)
|
|
#expect(result?.from == .tableau(0))
|
|
#expect(result?.to == .foundation(0))
|
|
}
|
|
|
|
@Test("Returns nil when no moves available")
|
|
func noMovesAvailable() {
|
|
let snapshot = emptySnapshot()
|
|
let result = AutoCompleter.findNextAutoMove(state: snapshot, rules: rules)
|
|
#expect(result == nil)
|
|
}
|
|
|
|
@Test("Finds waste pile ace")
|
|
func findWasteAce() {
|
|
let ace = Card(suit: .spades, rank: .ace, isFaceUp: true)
|
|
var snapshot = emptySnapshot()
|
|
snapshot.waste = [ace]
|
|
|
|
let result = AutoCompleter.findNextAutoMove(state: snapshot, rules: rules)
|
|
#expect(result != nil)
|
|
#expect(result?.from == .waste)
|
|
}
|
|
|
|
@Test("Finds free cell card for FreeCell variant")
|
|
func findFreeCellMove() {
|
|
let freeCellRules = FreeCellRules()
|
|
let ace = Card(suit: .diamonds, rank: .ace, isFaceUp: true)
|
|
var snapshot = GameSnapshot(
|
|
tableaus: Array(repeating: [], count: 8),
|
|
foundations: [[], [], [], []],
|
|
stock: [],
|
|
waste: [],
|
|
freeCells: [ace, nil, nil, nil],
|
|
moves: 0,
|
|
score: 0
|
|
)
|
|
|
|
let result = AutoCompleter.findNextAutoMove(state: snapshot, rules: freeCellRules)
|
|
#expect(result != nil)
|
|
#expect(result?.from == .freeCell(0))
|
|
}
|
|
|
|
private func emptySnapshot() -> GameSnapshot {
|
|
GameSnapshot(
|
|
tableaus: Array(repeating: [], count: 7),
|
|
foundations: [[], [], [], []],
|
|
stock: [],
|
|
waste: [],
|
|
freeCells: [],
|
|
moves: 0,
|
|
score: 0
|
|
)
|
|
}
|
|
}
|