import Testing import Foundation @testable import 康康 struct SeriesBucketTests { private func makeIndicator( name: String = "测试", value: String, unit: String = "mmol/L", range: String = "", status: IndicatorStatus = .normal, capturedAt: Date, seriesKey: String? ) -> Indicator { Indicator(name: name, value: value, unit: unit, range: range, status: status, capturedAt: capturedAt, seriesKey: seriesKey) } @Test func skipsIndicatorsWithoutSeriesKey() { let now = Date() let items = [ makeIndicator(value: "5.0", capturedAt: now, seriesKey: nil), makeIndicator(value: "5.2", capturedAt: now, seriesKey: nil), ] let buckets = SeriesBucket.build(from: items) #expect(buckets.isEmpty) } @Test func filtersOutSeriesWithFewerThanMinPoints() { let now = Date() let items = [ makeIndicator(value: "5.0", capturedAt: now, seriesKey: "glucose.fasting"), ] let buckets = SeriesBucket.build(from: items, minPoints: 2) #expect(buckets.isEmpty) } @Test func singleSeriesBucketSortedAscending() { let day = { (offset: Int) -> Date in Calendar.current.date(byAdding: .day, value: offset, to: .now)! } let items = [ makeIndicator(value: "5.5", capturedAt: day(-3), seriesKey: "glucose.fasting"), makeIndicator(value: "5.2", capturedAt: day(-1), seriesKey: "glucose.fasting"), makeIndicator(value: "5.8", capturedAt: day(-2), seriesKey: "glucose.fasting"), ] let buckets = SeriesBucket.build(from: items) #expect(buckets.count == 1) let line = try! #require(buckets.first?.lines.first) // sorted ascending → -3, -2, -1 let values = line.points.map(\.value) #expect(values == [5.5, 5.8, 5.2]) } @Test func bloodPressureMergesIntoSingleBucket() { let now = Date() let day = { (offset: Int) -> Date in Calendar.current.date(byAdding: .day, value: offset, to: now)! } let items = [ makeIndicator(value: "125", capturedAt: day(-2), seriesKey: "bp.systolic"), makeIndicator(value: "82", capturedAt: day(-2), seriesKey: "bp.diastolic"), makeIndicator(value: "130", capturedAt: day(-1), seriesKey: "bp.systolic"), makeIndicator(value: "85", capturedAt: day(-1), seriesKey: "bp.diastolic"), ] let buckets = SeriesBucket.build(from: items) let bp = try! #require(buckets.first { $0.id == "bp" }) #expect(bp.lines.count == 2) #expect(bp.title == "血压") #expect(bp.lines.contains { $0.seriesKey == "bp.systolic" }) #expect(bp.lines.contains { $0.seriesKey == "bp.diastolic" }) } @Test func mixedSeriesProducesMultipleBucketsSortedByRecency() { let cal = Calendar.current let day = { (offset: Int) -> Date in cal.date(byAdding: .day, value: offset, to: .now)! } let items = [ // weight 较旧 makeIndicator(value: "68", capturedAt: day(-10), seriesKey: "weight"), makeIndicator(value: "67", capturedAt: day(-7), seriesKey: "weight"), // glucose 较新 makeIndicator(value: "5.1", capturedAt: day(-2), seriesKey: "glucose.fasting"), makeIndicator(value: "5.3", capturedAt: day(-1), seriesKey: "glucose.fasting"), ] let buckets = SeriesBucket.build(from: items) #expect(buckets.count == 2) // 最新的 glucose 排前面 #expect(buckets.first?.id == "glucose.fasting") #expect(buckets.last?.id == "weight") } @Test func nonNumericValueDropped() { let now = Date() let items = [ makeIndicator(value: "高", capturedAt: now, seriesKey: "weight"), makeIndicator(value: "68", capturedAt: now, seriesKey: "weight"), makeIndicator(value: "67", capturedAt: now.addingTimeInterval(60), seriesKey: "weight"), ] let buckets = SeriesBucket.build(from: items) let line = try! #require(buckets.first?.lines.first) #expect(line.points.count == 2) // "高" 被丢 } }