import Foundation import Testing import RunnerCore struct RunnerCoreTests { // MARK: - AgeGroup @Test func ageGroupDescription() { #expect(AgeGroup.junior.description == "Junior") #expect(AgeGroup.adult.description == "Adult") #expect(AgeGroup.senior.description == "Senior") } @Test func ageGroupRawValueInit() { #expect(AgeGroup(rawValue: 1) == .junior) #expect(AgeGroup(rawValue: 3) == .senior) #expect(AgeGroup(rawValue: 0) == nil) #expect(AgeGroup(rawValue: 4) == nil) } // MARK: - SortKey.sort private func runner(_ name: String, _ group: AgeGroup, _ time: String, _ nr: Int) -> Runner { Runner(name: name, ageGroup: group, time: time, number: nr) } @Test func sortKeyByName() { let a = runner("Bob", .adult, "10:00", 2) let b = runner("Ann", .senior, "09:00", 1) #expect(SortKey.name.sort(b, a)) #expect(!SortKey.name.sort(a, b)) } @Test func sortKeyByAgeGroup() { let junior = runner("X", .junior, "10:00", 5) let senior = runner("A", .senior, "09:00", 1) #expect(SortKey.ageGroup.sort(junior, senior)) #expect(!SortKey.ageGroup.sort(senior, junior)) } @Test func sortKeyByTime() { let fast = runner("X", .adult, "09:00", 2) let slow = runner("A", .adult, "10:00", 1) #expect(SortKey.time.sort(fast, slow)) } @Test func sortKeyByNumber() { let first = runner("Z", .adult, "10:00", 1) let second = runner("A", .adult, "09:00", 2) #expect(SortKey.number.sort(first, second)) } // MARK: - readFile @Test func readFileReturnsContents() throws { let dir = FileManager.default.temporaryDirectory let url = dir.appendingPathComponent("runnercore-test-\(UUID().uuidString).txt") try "hello\nworld".write(to: url, atomically: true, encoding: .utf8) defer { try? FileManager.default.removeItem(at: url) } #expect(readFile(url.path) == "hello\nworld") } @Test func readFileMissingReturnsEmpty() { #expect(readFile("/no/such/path/\(UUID().uuidString)") == "") } // MARK: - parseResults @Test func parseResultsValidLines() { let content = "1\t09:30\n2\t10:15" let results = parseResults(content, separator: "\t") #expect(results == [1: "09:30", 2: "10:15"]) } @Test func parseResultsSkipsMalformed() { let content = "1\t09:30\nnope\n\n3" // "nope" non-int, empty, "3" has <2 cols let results = parseResults(content, separator: "\t") #expect(results == [1: "09:30"]) } @Test func parseResultsDuplicateKeyKeepsLast() { let content = "1\tfirst\n1\tsecond" let results = parseResults(content, separator: "\t") #expect(results == [1: "second"]) } // MARK: - parseStarters @Test func parseStartersValidLines() { let content = "1\t2\tAlice\n7\t3\tBob" let starters = parseStarters(content, separator: "\t") #expect(starters.count == 2) #expect(starters[0].nr == 1) #expect(starters[0].ageGroup == 2) #expect(starters[0].name == "Alice") #expect(starters[1].name == "Bob") } @Test func parseStartersSkipsMalformed() { let content = "1\t2\tAlice\nx\t2\tBad\n5\t2" // non-int nr, too few cols let starters = parseStarters(content, separator: "\t") #expect(starters.count == 1) #expect(starters[0].name == "Alice") } // MARK: - assembleRunners @Test func assembleRunnersMatchesResults() { let starters = [(nr: 1, ageGroup: 2, name: "Alice"), (nr: 2, ageGroup: 3, name: "Bob")] let results = [1: "09:30", 2: "10:00"] let runners = assembleRunners(starters: starters, results: results) #expect(runners.count == 2) #expect(runners[0] == Runner(name: "Alice", ageGroup: .adult, time: "09:30", number: 1)) } @Test func assembleRunnersSkipsMissingResultAndBadGroup() { let starters = [ (nr: 1, ageGroup: 2, name: "Alice"), (nr: 2, ageGroup: 9, name: "Bob"), ] let results = [2: "10:00"] let runners = assembleRunners(starters: starters, results: results) #expect(runners.isEmpty) } // MARK: - nonParticipants @Test func nonParticipantsGetDNS() { let starters = [(nr: 1, ageGroup: 2, name: "Alice"), (nr: 2, ageGroup: 3, name: "Bob")] let results = [2: "10:00"] // only Bob finished let absent = nonParticipants(starters: starters, results: results) #expect(absent == [Runner(name: "Alice", ageGroup: .adult, time: "DNS", number: 1)]) } @Test func nonParticipantsSkipsBadGroup() { let starters = [(nr: 1, ageGroup: 9, name: "Ghost")] let absent = nonParticipants(starters: starters, results: [:]) #expect(absent.isEmpty) } // MARK: - visibleRunners private var sample: [Runner] { [ runner("Alice", .junior, "09:00", 1), runner("Bob", .adult, "10:00", 2), runner("Charlie", .senior, "08:00", 3), ] } @Test func visibleRunnersFiltersByGroup() { let visible = visibleRunners( sample, query: "", includedGroups: [.adult], sortKey: .number) #expect(visible.map(\.name) == ["Bob"]) } @Test func visibleRunnersQueryIsCaseInsensitive() { let visible = visibleRunners( sample, query: "aLiC", includedGroups: Set(AgeGroup.allCases), sortKey: .number) #expect(visible.map(\.name) == ["Alice"]) } @Test func visibleRunnersEmptyQueryReturnsAllSorted() { let visible = visibleRunners( sample, query: "", includedGroups: Set(AgeGroup.allCases), sortKey: .time) #expect(visible.map(\.name) == ["Charlie", "Alice", "Bob"]) } @Test func visibleRunnersEmptyWhenNoGroups() { let visible = visibleRunners( sample, query: "", includedGroups: [], sortKey: .number) #expect(visible.isEmpty) } // MARK: - toggling @Test func togglingAddsWhenAbsent() { #expect(toggling([.junior], .adult) == [.junior, .adult]) } @Test func togglingRemovesWhenPresent() { #expect(toggling([.junior, .adult], .adult) == [.junior]) } // MARK: - menuLabel @Test func menuLabelOn() { #expect(menuLabel("Name", on: true) == "[Name]") } @Test func menuLabelOff() { #expect(menuLabel("Name", on: false) == " Name ") } // MARK: - formatRow @Test func formatRowPadsColumns() { let row = formatRow("#1", "Alice", "Junior", "09:00") #expect(row == "#1 Alice Junior 09:00") #expect(row == "#1".padding(toLength: 6, withPad: " ", startingAt: 0) + "Alice".padding(toLength: 22, withPad: " ", startingAt: 0) + "Junior".padding(toLength: 9, withPad: " ", startingAt: 0) + "09:00") } @Test func formatRowTruncatesLongFields() { // padding(toLength:) truncates when the source is longer than the target. let longName = String(repeating: "x", count: 30) let row = formatRow("#1", longName, "Junior", "09:00") #expect(row.contains(String(repeating: "x", count: 22))) #expect(!row.contains(String(repeating: "x", count: 23))) } }