This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
120 lines
4.3 KiB
Swift
120 lines
4.3 KiB
Swift
//
|
|
// StackDexTests.swift
|
|
// StackDexTests
|
|
//
|
|
// Created by Matthias Meister on 18.04.26.
|
|
//
|
|
|
|
import Testing
|
|
@testable import StackDex
|
|
import UIKit
|
|
|
|
struct StackDexTests {
|
|
|
|
@Test func cameraSessionStateBlocksStartUntilConfigurationCommits() {
|
|
var state = CameraSessionState()
|
|
|
|
#expect(state.canStartSession)
|
|
|
|
state.beginConfiguration()
|
|
#expect(!state.canStartSession)
|
|
|
|
state.commitConfiguration()
|
|
#expect(state.canStartSession)
|
|
}
|
|
|
|
@Test func cameraSessionStateTracksNestedConfigurationDepth() {
|
|
var state = CameraSessionState()
|
|
|
|
state.beginConfiguration()
|
|
state.beginConfiguration()
|
|
#expect(!state.canStartSession)
|
|
|
|
state.commitConfiguration()
|
|
#expect(!state.canStartSession)
|
|
|
|
state.commitConfiguration()
|
|
#expect(state.canStartSession)
|
|
}
|
|
|
|
@Test func heuristicExtractorFindsStructuredFields() {
|
|
let extractor = CardTextHeuristicExtractor()
|
|
let payload = OCRTextPayload(
|
|
rawText: "Charizard\n120 HP\n4/102 Base Set\nHolo Rare",
|
|
lines: [
|
|
RecognizedTextLine(text: "Charizard", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.82, width: 0.4, height: 0.08)),
|
|
RecognizedTextLine(text: "120 HP", confidence: 0.81, normalizedBounds: CGRect(x: 0.75, y: 0.83, width: 0.12, height: 0.05)),
|
|
RecognizedTextLine(text: "4/102 Base Set", confidence: 0.92, normalizedBounds: CGRect(x: 0.2, y: 0.12, width: 0.4, height: 0.05)),
|
|
RecognizedTextLine(text: "Holo Rare", confidence: 0.88, normalizedBounds: CGRect(x: 0.7, y: 0.1, width: 0.2, height: 0.05)),
|
|
],
|
|
averageConfidence: 0.89
|
|
)
|
|
|
|
let draft = extractor.extract(payload: payload, source: .onDeviceOffline)
|
|
|
|
#expect(draft.cardName == "Charizard")
|
|
#expect(draft.cardNumber == "4/102")
|
|
#expect(draft.setIdentifier == "Base Set")
|
|
#expect(draft.rarity == CardRarity.holoRare.rawValue)
|
|
#expect(draft.confidence == .high)
|
|
}
|
|
|
|
@Test func pipelineFallsBackToVisionWhenCloudIsStubbed() async throws {
|
|
let monitor = TestNetworkStatusProvider(isOnline: true)
|
|
let pipeline = CardRecognitionPipeline(
|
|
networkStatusProvider: monitor,
|
|
cloudOCRClient: StubCloudOCRClient(),
|
|
fallbackOCR: TestOCRService(payload: OCRTextPayload(
|
|
rawText: "Pikachu\n25/165\nScarlet & Violet\nRare",
|
|
lines: [
|
|
RecognizedTextLine(text: "Pikachu", confidence: 0.95, normalizedBounds: CGRect(x: 0.1, y: 0.8, width: 0.3, height: 0.08)),
|
|
RecognizedTextLine(text: "25/165", confidence: 0.91, normalizedBounds: CGRect(x: 0.3, y: 0.15, width: 0.18, height: 0.05)),
|
|
RecognizedTextLine(text: "Scarlet & Violet", confidence: 0.87, normalizedBounds: CGRect(x: 0.25, y: 0.11, width: 0.4, height: 0.05)),
|
|
RecognizedTextLine(text: "Rare", confidence: 0.75, normalizedBounds: CGRect(x: 0.76, y: 0.1, width: 0.12, height: 0.04)),
|
|
],
|
|
averageConfidence: 0.87
|
|
))
|
|
)
|
|
|
|
let session = try await pipeline.recognizeCard(in: TestImageFactory.makeImage())
|
|
|
|
#expect(session.draft.source == .onDeviceFallback)
|
|
#expect(session.draft.cardName == "Pikachu")
|
|
#expect(session.draft.cardNumber == "25/165")
|
|
#expect(session.draft.notes.contains(where: { $0.contains("Cloud OCR") }))
|
|
}
|
|
|
|
}
|
|
|
|
private final class TestNetworkStatusProvider: NetworkStatusProviding {
|
|
var isOnline: Bool
|
|
|
|
init(isOnline: Bool) {
|
|
self.isOnline = isOnline
|
|
}
|
|
|
|
func startMonitoring() {}
|
|
func stopMonitoring() {}
|
|
}
|
|
|
|
private struct TestOCRService: CardTextRecognizing {
|
|
let payload: OCRTextPayload
|
|
|
|
func recognizeText(in image: PreparedImage) async throws -> OCRTextPayload {
|
|
payload
|
|
}
|
|
}
|
|
|
|
private enum TestImageFactory {
|
|
static func makeImage() -> UIImage {
|
|
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 300, height: 420))
|
|
return renderer.image { context in
|
|
UIColor.white.setFill()
|
|
context.fill(CGRect(x: 0, y: 0, width: 300, height: 420))
|
|
UIColor.black.setStroke()
|
|
context.cgContext.setLineWidth(6)
|
|
context.cgContext.stroke(CGRect(x: 16, y: 16, width: 268, height: 388))
|
|
}
|
|
}
|
|
}
|