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>
108 lines
2.8 KiB
Swift
108 lines
2.8 KiB
Swift
import Testing
|
|
@testable import SoliCards
|
|
|
|
@Suite("GameState Tests")
|
|
struct GameStateTests {
|
|
|
|
@Test("Snapshot captures current state")
|
|
func snapshotCapture() {
|
|
let state = GameState()
|
|
let card = Card(suit: .hearts, rank: .ace, isFaceUp: true)
|
|
state.tableaus = [[card]]
|
|
state.foundations = [[], [], [], []]
|
|
state.moves = 5
|
|
state.score = 100
|
|
|
|
let snapshot = state.snapshot()
|
|
#expect(snapshot.tableaus[0].count == 1)
|
|
#expect(snapshot.moves == 5)
|
|
#expect(snapshot.score == 100)
|
|
}
|
|
|
|
@Test("Restore from snapshot")
|
|
func restoreFromSnapshot() {
|
|
let state = GameState()
|
|
let card = Card(suit: .spades, rank: .king, isFaceUp: true)
|
|
let snapshot = GameSnapshot(
|
|
tableaus: [[card]],
|
|
foundations: [[], [], [], []],
|
|
stock: [],
|
|
waste: [],
|
|
freeCells: [],
|
|
moves: 10,
|
|
score: 50
|
|
)
|
|
|
|
state.restore(from: snapshot)
|
|
#expect(state.tableaus[0].count == 1)
|
|
#expect(state.moves == 10)
|
|
#expect(state.score == 50)
|
|
}
|
|
|
|
@Test("Push and pop history")
|
|
func historyStack() {
|
|
let state = GameState()
|
|
state.tableaus = [[]]
|
|
state.foundations = [[], [], [], []]
|
|
state.moves = 0
|
|
state.score = 0
|
|
|
|
state.pushHistory()
|
|
state.moves = 5
|
|
|
|
#expect(state.canUndo)
|
|
let restored = state.popHistory()
|
|
#expect(restored != nil)
|
|
#expect(restored?.moves == 0)
|
|
#expect(!state.canUndo)
|
|
}
|
|
|
|
@Test("History caps at 20 entries")
|
|
func historyCap() {
|
|
let state = GameState()
|
|
state.tableaus = [[]]
|
|
state.foundations = [[], [], [], []]
|
|
|
|
for i in 0..<25 {
|
|
state.moves = i
|
|
state.pushHistory()
|
|
}
|
|
|
|
#expect(state.history.count == 20)
|
|
// First entries should have been removed
|
|
#expect(state.history.first?.moves == 5)
|
|
}
|
|
|
|
@Test("Clear history")
|
|
func clearHistory() {
|
|
let state = GameState()
|
|
state.tableaus = [[]]
|
|
state.foundations = [[], [], [], []]
|
|
state.pushHistory()
|
|
state.pushHistory()
|
|
#expect(state.canUndo)
|
|
|
|
state.clearHistory()
|
|
#expect(!state.canUndo)
|
|
#expect(state.history.isEmpty)
|
|
}
|
|
|
|
@Test("Reset from snapshot sets phase to playing")
|
|
func resetSetsPlaying() {
|
|
let state = GameState()
|
|
let snapshot = GameSnapshot(
|
|
tableaus: Array(repeating: [], count: 7),
|
|
foundations: [[], [], [], []],
|
|
stock: [],
|
|
waste: [],
|
|
freeCells: [],
|
|
moves: 0,
|
|
score: 0
|
|
)
|
|
|
|
state.reset(from: snapshot, variant: .klondike)
|
|
#expect(state.phase == .playing)
|
|
#expect(state.history.isEmpty)
|
|
}
|
|
}
|