Files
kangkang/康康Tests/CustomMonitorMetricTests.swift
link2026 1b01923c8e feat(capture): 统一报告捕获流程并集成视觉语言模型识别
- 替换 QuickCaptureFlow 和 ArchiveFlow 为 UnifiedCaptureFlow 统一流程
- 新增 VLSession 封装 Qwen2.5-VL 模型进行图像文本推理
- 实现 AIRuntime 中 VL 模型的准备和分析功能
- 添加 VLPrompts 定义体检化验单识别的 JSON 输出模板
- 创建 CaptureReviewForm 提供 VL 解析结果的可编辑表单界面
- 集成 VisionKit 文档扫描器支持真机多页文档扫描
- 为模拟器实现 PhotosPicker 回退方案选择已有照片
- 在 RootView 中统一使用 UnifiedCaptureFlow 处理快速和归档流程
- 添加 CustomMetricEditor 支持自定义监测指标的创建编辑删除
- 扩展 KangkangApp 模型配置以支持新数据类型
- 实现档案列表中症状结束功能通过时间线行点击触发
2026-05-26 11:18:00 +08:00

146 lines
5.7 KiB
Swift

import Testing
import SwiftData
import Foundation
@testable import
struct CustomMonitorMetricTests {
private func makeContainer() throws -> ModelContainer {
let schema = Schema([
CustomMonitorMetric.self,
Indicator.self,
])
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
return try ModelContainer(for: schema, configurations: [config])
}
@Test func seriesKeyAutoPrefixedWithCustom() {
let m = CustomMonitorMetric(name: "腰围", unit: "cm")
#expect(m.seriesKey.hasPrefix("custom."))
#expect(m.seriesKey.count > "custom.".count)
}
@Test func seriesKeyUniquePerInstance() {
let a = CustomMonitorMetric(name: "腰围", unit: "cm")
let b = CustomMonitorMetric(name: "腰围", unit: "cm")
#expect(a.seriesKey != b.seriesKey)
}
@Test func referenceRangeNilWhenBoundsAbsentOrInverted() {
let none = CustomMonitorMetric(name: "x", unit: "")
#expect(none.referenceRange == nil)
let inverted = CustomMonitorMetric(name: "x", unit: "", lowerBound: 100, upperBound: 50)
#expect(inverted.referenceRange == nil)
let valid = CustomMonitorMetric(name: "x", unit: "", lowerBound: 60, upperBound: 100)
#expect(valid.referenceRange == 60...100)
}
@Test func rangeTextFormattingDropsTrailingZero() {
let intRange = CustomMonitorMetric(name: "x", unit: "cm",
lowerBound: 70, upperBound: 90)
#expect(intRange.rangeText == "70 - 90")
let decimalRange = CustomMonitorMetric(name: "y", unit: "kg",
lowerBound: 60.5, upperBound: 65.5)
#expect(decimalRange.rangeText == "60.5 - 65.5")
}
@Test func roundtripsThroughSwiftData() throws {
let container = try makeContainer()
let ctx = ModelContext(container)
let m = CustomMonitorMetric(name: "腰围", unit: "cm",
lowerBound: 70, upperBound: 90,
icon: "flame.fill")
ctx.insert(m)
try ctx.save()
let fetched = try #require(try ctx.fetch(FetchDescriptor<CustomMonitorMetric>()).first)
#expect(fetched.name == "腰围")
#expect(fetched.unit == "cm")
#expect(fetched.lowerBound == 70)
#expect(fetched.upperBound == 90)
#expect(fetched.icon == "flame.fill")
#expect(fetched.seriesKey.hasPrefix("custom."))
}
@Test func seriesBucketResolvesCustomTitleAndRange() {
let custom = CustomMonitorMetric(name: "腰围", unit: "cm",
lowerBound: 70, upperBound: 90)
let key = custom.seriesKey
let now = Date()
let day = { (offset: Int) -> Date in
Calendar.current.date(byAdding: .day, value: offset, to: now)!
}
let items = [
Indicator(name: "腰围", value: "80", unit: "cm", range: "70-90",
status: .normal, capturedAt: day(-2), seriesKey: key),
Indicator(name: "腰围", value: "82", unit: "cm", range: "70-90",
status: .normal, capturedAt: day(-1), seriesKey: key),
]
let buckets = SeriesBucket.build(from: items, customMetrics: [custom])
#expect(buckets.count == 1)
let b = try! #require(buckets.first)
#expect(b.title == "腰围")
#expect(b.unit == "cm")
#expect(b.lines.first?.referenceRange == 70...90)
}
@Test func nameConflictEmptyNameYieldsNone() {
let result = detectNameConflict(candidate: " ", customs: [])
#expect(result == .none)
}
@Test func nameConflictDetectsBuiltinMatch() {
let result = detectNameConflict(candidate: "血压", customs: [])
#expect(result == .builtin("血压"))
}
@Test func nameConflictBuiltinIgnoresWhitespace() {
let result = detectNameConflict(candidate: " 空腹血糖 ", customs: [])
#expect(result == .builtin("空腹血糖"))
}
@Test func nameConflictDetectsExistingCustom() {
let existing = CustomMonitorMetric(name: "腰围", unit: "cm")
let result = detectNameConflict(candidate: "腰围", customs: [existing])
#expect(result == .existingCustom("腰围"))
}
@Test func nameConflictAllowsRenamingSelf() {
// ,使
let me = CustomMonitorMetric(name: "腰围", unit: "cm")
let result = detectNameConflict(
candidate: "腰围",
customs: [me],
excludingSeriesKey: me.seriesKey
)
#expect(result == .none)
}
@Test func nameConflictUnique() {
let result = detectNameConflict(candidate: "步数", customs: [])
#expect(result == .none)
}
@Test func seriesBucketFallsBackToIndicatorNameWhenCustomMissing() {
// CustomMonitorMetric Indicator title fallback indicator.name
let orphanKey = "custom.deleted-xxxxx"
let now = Date()
let items = [
Indicator(name: "睡眠时长", value: "7", unit: "h", range: "",
status: .normal, capturedAt: now, seriesKey: orphanKey),
Indicator(name: "睡眠时长", value: "8", unit: "h", range: "",
status: .normal, capturedAt: now.addingTimeInterval(60),
seriesKey: orphanKey),
]
let buckets = SeriesBucket.build(from: items, customMetrics: [])
let b = try! #require(buckets.first)
#expect(b.title == "睡眠时长")
#expect(b.unit == "h")
#expect(b.lines.first?.referenceRange == nil)
}
}