Aggiunge app iOS nativa con parità multi-sport e test unitari.
Porting SwiftUI da Android (auth, hub, wizard, broadcast RTMP, overlay, scoring board-aware), reload hub al ritorno da diretta, decodifica score tollerante e documentazione allineata. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
47
native/ios/MatchLiveTvTests/ApiInstantTests.swift
Normal file
47
native/ios/MatchLiveTvTests/ApiInstantTests.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
import XCTest
|
||||
|
||||
/// Smoke test eseguibile senza @testable import (validazione logica pura).
|
||||
final class ApiInstantTests: XCTestCase {
|
||||
func testOverlayKindMapping() {
|
||||
XCTAssertEqual(OverlayKindMapping.fromApi("basket"), "basket")
|
||||
XCTAssertEqual(OverlayKindMapping.fromApi("none"), "none")
|
||||
XCTAssertEqual(OverlayKindMapping.fromApi(nil), "volley")
|
||||
}
|
||||
|
||||
func testRTMPUrlParser() {
|
||||
let target = RTMPUrlParserLogic.parse("rtmp://stream.example.com/live/match_abc-123")
|
||||
XCTAssertEqual(target?.connectURL, "rtmp://stream.example.com/live")
|
||||
XCTAssertEqual(target?.streamName, "match_abc-123")
|
||||
}
|
||||
}
|
||||
|
||||
enum OverlayKindMapping {
|
||||
static func fromApi(_ value: String?) -> String {
|
||||
switch value?.lowercased() {
|
||||
case "none", "timer": return "none"
|
||||
case "basket": return "basket"
|
||||
case "timed": return "timed"
|
||||
case "racket": return "racket"
|
||||
default: return "volley"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RTMPUrlParserLogic {
|
||||
struct Target {
|
||||
let connectURL: String
|
||||
let streamName: String
|
||||
}
|
||||
|
||||
static func parse(_ urlString: String) -> Target? {
|
||||
guard let url = URL(string: urlString), url.scheme?.hasPrefix("rtmp") == true else { return nil }
|
||||
let pathParts = url.path.split(separator: "/").map(String.init)
|
||||
guard pathParts.count >= 2 else { return nil }
|
||||
let app = pathParts.dropLast().joined(separator: "/")
|
||||
let streamName = pathParts.last ?? ""
|
||||
var connect = "rtmp://\(url.host ?? "")"
|
||||
if let port = url.port { connect += ":\(port)" }
|
||||
connect += "/\(app)"
|
||||
return Target(connectURL: connect, streamName: streamName)
|
||||
}
|
||||
}
|
||||
140
native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift
Normal file
140
native/ios/MatchLiveTvTests/MatchScoringRulesTests.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
import XCTest
|
||||
@testable import MatchLiveTv
|
||||
|
||||
final class MatchScoringRulesTests: XCTestCase {
|
||||
private func volleyMatch(setsToWin: Int = 3, rules: ScoringRules? = nil) -> Match {
|
||||
Match(
|
||||
id: "match-1",
|
||||
teamId: "team-1",
|
||||
teamName: "Tigers Volley",
|
||||
opponentName: "Avversario",
|
||||
location: nil,
|
||||
scheduledAt: nil,
|
||||
sportKey: "pallavolo",
|
||||
sportLabel: "Pallavolo",
|
||||
boardType: "volley",
|
||||
overlayKind: "volley",
|
||||
effectiveOverlayKind: "volley",
|
||||
setsToWin: setsToWin,
|
||||
category: nil,
|
||||
scoringRules: rules,
|
||||
activeSessionId: nil,
|
||||
activeSessionStatus: nil,
|
||||
streamCompleted: false,
|
||||
coachHubVisible: nil,
|
||||
homePrimaryColor: "#FF2D2D",
|
||||
homeSecondaryColor: nil,
|
||||
homeLogoUrl: nil,
|
||||
opponentPrimaryColor: "#1E3A8A",
|
||||
opponentLogoUrl: nil
|
||||
)
|
||||
}
|
||||
|
||||
func testSetWinnerAt26_12() {
|
||||
let rules = MatchScoringContext(match: volleyMatch())
|
||||
XCTAssertEqual(rules.setWinnerFromPoints(homePoints: 26, awayPoints: 12, currentSet: 1), .home)
|
||||
}
|
||||
|
||||
func testSetWinnerRequiresMinLead() {
|
||||
let rules = MatchScoringContext(match: volleyMatch())
|
||||
XCTAssertNil(rules.setWinnerFromPoints(homePoints: 25, awayPoints: 24, currentSet: 1))
|
||||
XCTAssertEqual(rules.setWinnerFromPoints(homePoints: 25, awayPoints: 23, currentSet: 1), .home)
|
||||
}
|
||||
|
||||
func testDecidingSetUses15Points() {
|
||||
let rules = MatchScoringContext(match: volleyMatch(setsToWin: 3))
|
||||
XCTAssertEqual(rules.maxSets, 5)
|
||||
XCTAssertEqual(rules.pointsTarget(currentSet: 5), 15)
|
||||
XCTAssertEqual(rules.setWinnerFromPoints(homePoints: 15, awayPoints: 13, currentSet: 5), .home)
|
||||
}
|
||||
|
||||
func testMatchWinnerAfterThreeSets() {
|
||||
let rules = MatchScoringContext(match: volleyMatch(setsToWin: 3))
|
||||
XCTAssertEqual(rules.matchWinner(homeSets: 3, awaySets: 1), .home)
|
||||
XCTAssertNil(rules.matchWinner(homeSets: 2, awaySets: 1))
|
||||
}
|
||||
|
||||
func testCustomScoringRulesFromMatch() {
|
||||
let custom = ScoringRules(pointsPerSet: 21, pointsDecidingSet: 15, minPointLead: 2)
|
||||
let rules = MatchScoringContext(match: volleyMatch(setsToWin: 2, rules: custom))
|
||||
XCTAssertEqual(rules.pointsTarget(currentSet: 1), 21)
|
||||
XCTAssertEqual(rules.setWinnerFromPoints(homePoints: 21, awayPoints: 19, currentSet: 1), .home)
|
||||
}
|
||||
|
||||
func testUsesSetLogicOnlyForVolleyAndRacket() {
|
||||
XCTAssertTrue(MatchScoringContext(match: volleyMatch()).usesSetLogic)
|
||||
var basket = volleyMatch()
|
||||
basket = Match(
|
||||
id: basket.id,
|
||||
teamId: basket.teamId,
|
||||
teamName: basket.teamName,
|
||||
opponentName: basket.opponentName,
|
||||
location: basket.location,
|
||||
scheduledAt: basket.scheduledAt,
|
||||
sportKey: "basket",
|
||||
sportLabel: "Basket",
|
||||
boardType: "basket",
|
||||
overlayKind: "basket",
|
||||
effectiveOverlayKind: "basket",
|
||||
setsToWin: 0,
|
||||
category: nil,
|
||||
scoringRules: nil,
|
||||
activeSessionId: nil,
|
||||
activeSessionStatus: nil,
|
||||
streamCompleted: false,
|
||||
coachHubVisible: nil,
|
||||
homePrimaryColor: nil,
|
||||
homeSecondaryColor: nil,
|
||||
homeLogoUrl: nil,
|
||||
opponentPrimaryColor: nil,
|
||||
opponentLogoUrl: nil
|
||||
)
|
||||
XCTAssertFalse(MatchScoringContext(match: basket).usesSetLogic)
|
||||
}
|
||||
|
||||
func testDecodesFlexibleScorePayload() throws {
|
||||
let json = """
|
||||
{
|
||||
"board_type": "basket",
|
||||
"home_points": 42,
|
||||
"away_points": 38,
|
||||
"current_set": 1,
|
||||
"period": "Q3",
|
||||
"clock_secs": 312,
|
||||
"clock_running": 1,
|
||||
"data": { "home_score": 42, "away_score": 38, "period": 3, "overtime": false }
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
let dto = try JSONDecoder().decode(ScoreStateDto.self, from: json)
|
||||
let score = dto.toDomain()
|
||||
XCTAssertEqual(score.boardType, "basket")
|
||||
XCTAssertEqual(score.homePoints, 42)
|
||||
XCTAssertEqual(score.periodLabel, "Q3")
|
||||
XCTAssertEqual(score.clockSecs, 312)
|
||||
XCTAssertTrue(score.clockRunning)
|
||||
}
|
||||
}
|
||||
|
||||
final class ScoreboardOverlayTests: XCTestCase {
|
||||
func testScoreboardColumnsAfterSetClosed() {
|
||||
let score = ScoreState(
|
||||
homeSets: 1,
|
||||
awaySets: 0,
|
||||
homePoints: 0,
|
||||
awayPoints: 0,
|
||||
currentSet: 2,
|
||||
setPartials: [SetPartial(set: 1, home: 26, away: 12)],
|
||||
boardType: "volley"
|
||||
)
|
||||
let board = score.toScoreboardState(homeTeamName: "Casa", awayTeamName: "Ospite")
|
||||
XCTAssertEqual(board.columns.count, 2)
|
||||
XCTAssertEqual(board.columns[0].setNumber, 1)
|
||||
XCTAssertEqual(board.columns[0].homePoints, 26)
|
||||
XCTAssertEqual(board.columns[0].awayPoints, 12)
|
||||
XCTAssertFalse(board.columns[0].isCurrent)
|
||||
XCTAssertEqual(board.columns[1].setNumber, 2)
|
||||
XCTAssertTrue(board.columns[1].isCurrent)
|
||||
XCTAssertEqual(board.columns[1].homePoints, 0)
|
||||
XCTAssertEqual(board.columns[1].awayPoints, 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user