BlockGrouperTest.swift 86 lignes · 3891 octets
import XCTest
@testable import Card2vcf

final class BlockGrouperTest: XCTestCase {

    private func ligne(
        _ text: String,
        top: Int,
        height: Int = 30,
        left: Int = 0,
        right: Int = 300,
        confidence: Float = 90
    ) -> OcrLine {
        OcrLine(text: text, box: OcrBox(left: left, top: top, right: right, bottom: top + height), confidence: confidence)
    }

    func testListeVideAucunBloc() {
        XCTAssertEqual([], BlockGrouper.group([]))
    }

    func testTroisPavesSeparesDonnentTroisBlocs() {
        let lines = [
            ligne("Sonia MARTIN", top: 100, height: 40),
            ligne("Directrice commerciale", top: 150),
            ligne("561 allée des Noisetiers", top: 400),
            ligne("69760 Limonest", top: 440),
            ligne("Tél. 04 75 35 12 34", top: 700),
        ]
        let blocs = BlockGrouper.group(lines)
        XCTAssertEqual(3, blocs.count)
        XCTAssertEqual(["Sonia MARTIN", "Directrice commerciale"], blocs[0].lines.map(\.text))
        XCTAssertEqual(["561 allée des Noisetiers", "69760 Limonest"], blocs[1].lines.map(\.text))
        XCTAssertEqual(["Tél. 04 75 35 12 34"], blocs[2].lines.map(\.text))
    }

    func testLignesAdresseContiguesGroupees() {
        let blocs = BlockGrouper.group([
            ligne("561 allée des Noisetiers", top: 100),
            ligne("69760 Limonest - France", top: 140),
        ])
        XCTAssertEqual(1, blocs.count)
        XCTAssertEqual("561 allée des Noisetiers\n69760 Limonest - France", blocs[0].text())
    }

    func testDeuxColonnesNonFusionneesMemeSiEntrelaceesVerticalement() {
        let gauche1 = ligne("561 allée des Noisetiers", top: 100, left: 0, right: 300)
        let droite1 = ligne("Tél. 04 75 35 12 34", top: 110, left: 600, right: 900)
        let gauche2 = ligne("69760 Limonest", top: 140, left: 0, right: 300)
        let droite2 = ligne("contact@voltea.fr", top: 150, left: 600, right: 900)
        let blocs = BlockGrouper.group([gauche1, droite1, gauche2, droite2])
        XCTAssertEqual(2, blocs.count)
        let textes = blocs.map { $0.lines.map(\.text) }
        XCTAssertTrue(textes.contains(["561 allée des Noisetiers", "69760 Limonest"]), "colonne gauche intacte: \(textes)")
        XCTAssertTrue(textes.contains(["Tél. 04 75 35 12 34", "contact@voltea.fr"]), "colonne droite intacte: \(textes)")
    }

    func testHauteurMedianeRobusteAUneLigneGeante() {
        // Logo géant (h=200) : la médiane reste ~30, donc l'écart de 60 px sous le logo coupe.
        let blocs = BlockGrouper.group([
            ligne("VOLTEA", top: 0, height: 200),
            ligne("Sonia Martin", top: 260),
            ligne("Directrice", top: 300),
        ])
        XCTAssertEqual(2, blocs.count)
        XCTAssertEqual(["VOLTEA"], blocs[0].lines.map(\.text))
        XCTAssertEqual(["Sonia Martin", "Directrice"], blocs[1].lines.map(\.text))
    }

    func testBlocsOrdonnesDeHautEnBasPuisDeGaucheADroite() {
        let basGauche = ligne("bas gauche", top: 500, left: 0, right: 200)
        let hautDroite = ligne("haut droite", top: 100, left: 600, right: 800)
        let hautGauche = ligne("haut gauche", top: 100, left: 0, right: 200)
        let blocs = BlockGrouper.group([basGauche, hautDroite, hautGauche])
        XCTAssertEqual(["haut gauche", "haut droite", "bas gauche"], blocs.map { $0.text() })
    }

    func testProprietesDeBloc() {
        let bloc = OcrBlock(lines: [
            OcrLine(text: "a", box: OcrBox(left: 10, top: 100, right: 200, bottom: 130), confidence: 80),
            OcrLine(text: "b", box: OcrBox(left: 20, top: 140, right: 300, bottom: 170), confidence: 60),
        ])
        XCTAssertEqual(OcrBox(left: 10, top: 100, right: 300, bottom: 170), bloc.box())
        XCTAssertEqual(70, bloc.avgConfidence())
        XCTAssertEqual("a\nb", bloc.text())
    }
}