refactor(calendar): 合并节气与时令设置
This commit is contained in:
1 parent
3aae19a4c6
commit
1e0ff1236e
17 files changed
+80
-49
No files matched your search
+6
-4
@@ -36,11 +36,13 @@ its displayed count resets at local midnight even if the main app has not refres
|
||||
The tomorrow-work widget uses the official 2026 holiday schedule to distinguish statutory
|
||||
holidays, adjusted days off, ordinary weekends, and makeup workdays; later years need their
|
||||
own confirmed schedule before receiving those specific labels.
|
||||
The single seasonal-calendar setting covers dog days, traditional plum-rain days, and the
|
||||
nine cold periods. Month cells and the large month widget show only phase starts and the
|
||||
The combined solar-term and seasonal setting covers solar terms, dog days, traditional
|
||||
plum-rain days, and the nine cold periods. Month cells and the large month widget show solar
|
||||
terms on their dates, phase starts and the
|
||||
plum-rain exit; the small daily widget and selected-day details keep the exact day count.
|
||||
Existing dog-day or plum-rain
|
||||
preferences migrate to the combined setting.
|
||||
Existing solar-term, seasonal, dog-day or plum-rain preferences migrate to the combined
|
||||
`seasonalInfo` setting if any were enabled. The new key invalidates old widget summaries
|
||||
so both solar terms and seasonal annotations are regenerated.
|
||||
|
||||
Things that are easy to get wrong and are handled on purpose:
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ GitHub Actions 只在分支 push 且 `server/**` 发生变化时构建并发布
|
||||
有 Developer ID Application 证书时,Taskfile 会自动使用钥匙串中第一个证书的唯一指纹;否则由构建脚本逐层补齐 ad-hoc 签名与 entitlement。临时签名没有团队身份,不能加载启用团队验证的 Sparkle 动态库,因此仅此本地/CI 路径关闭 Hardened Runtime;正式 Developer ID 构建仍强制启用,且临时签名构建不能在线安装或操作特权辅助工具。
|
||||
桌面小组件与主应用通过 `group.work.12306.xstats` App Group 共享只读展示摘要。
|
||||
菜单栏日历在所有应用语言下均提供“日期与星期”“日期与农历”“农历”“自定义”显示方式;日期与农历按应用语言呈现,切换语言保留用户选择。自定义可自由组合 y/M/d 字段与分隔符,年份可省略,输入时显示预览并提示无效格式,独立于月历的星期开关;支持农历字号与对比度、可选悬停展开,按分钟刷新,休眠或关闭功能后停止。日程和到期提醒默认关闭,通过明确的授权按钮请求 EventKit 权限,只在日历面板可见时查询当前 42 格日期范围;只读展示,不修改日程,也不向 Widget、设置备份或服务端传递内容。列表 ID 仅保存在本机,显示偏好参与设置备份。节假日覆盖年份来自内置 Tyme 数据并随应用更新;假期倒计时与最多 3 天的请假建议只基于已公布的中国调休与双休安排,跨到未知年份时停止推算。
|
||||
日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、星期、大陆节假日与调休、传统与公历节日、节气、黄历及假期概览;其他地区(含台湾、香港、澳门)默认只启用星期。三伏/梅雨/数九、藏历与回历各地区均默认关闭。月历不再显示干支,也不提供对应开关;年月日干支只在启用黄历后的日期详情中显示,旧偏好与备份中的干支选项被忽略,其他选项保留。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”;黄历关闭后不再计算宜忌摘要。
|
||||
日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、星期、大陆节假日与调休、传统与公历节日、节气与时令、黄历及假期概览;其他地区(含台湾、香港、澳门)默认只启用星期。藏历与回历各地区均默认关闭。“节气与时令”统一控制二十四节气、三伏、梅雨与数九;月历只显示节气当天、时令阶段起点及出梅,单日详情与单日小组件保留具体天数。旧节气或时令开关任一开启即恢复为开启,全部关闭才恢复为关闭;使用新的设置键使 Widget 旧摘要失效并重新生成。月历不再显示干支,也不提供对应开关;年月日干支只在启用黄历后的日期详情中显示,旧偏好与备份中的干支选项被忽略,其他选项保留。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”;黄历关闭后不再计算宜忌摘要。
|
||||
月历使用固定六行布局,日期 ID 在生成日期时计算一次。面板打开时测量初始高度,后续翻月、详情与日程内容变化在内部滚动,不从内容几何回调反向调整窗口,避免窗口高度与懒布局估算形成反馈循环。
|
||||
月历计算仅缓存最近一个月份(包含周起始日和时区),供实际面板、高度测量和重复打开共用;翻月替换缓存,避免浏览历史导致内存累积。日程日期标记在工作 actor 内随查询结果生成,界面不再逐格扫描日程列表;连续 EventKit 通知按 300 毫秒窗口合并,关闭面板时取消待执行刷新。
|
||||
用于正式签名的 Apple Developer 团队须为主应用和 Widget 扩展启用同一个 App Group;
|
||||
|
||||
@@ -17,10 +17,9 @@ extension Translations {
|
||||
星期 Weekdays
|
||||
中国大陆节假日与调休 Mainland China holidays and makeup workdays
|
||||
传统与公历节日 Festivals
|
||||
二十四节气 Solar terms
|
||||
节气与时令 Solar terms and seasons
|
||||
三伏天 Dog days
|
||||
梅雨天 Plum-rain days
|
||||
三伏·梅雨·数九 Fu · Plum rain · Nines
|
||||
初伏 Early Fu
|
||||
中伏 Middle Fu
|
||||
末伏 Late Fu
|
||||
|
||||
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 أيام الأسبوع
|
||||
中国大陆节假日与调休 العطلات وأيام العمل التعويضية في البر الرئيسي للصين
|
||||
传统与公历节日 المناسبات التقليدية والميلادية
|
||||
二十四节气 الفصول الشمسية الأربعة والعشرون
|
||||
节气与时令 الفصول الشمسية والفترات الموسمية
|
||||
三伏天 أيام القيظ
|
||||
梅雨天 أيام أمطار البرقوق التقليدية
|
||||
三伏·梅雨·数九 فو · ميو · التسعات
|
||||
初伏 فو الأولى
|
||||
中伏 فو الوسطى
|
||||
末伏 فو الأخيرة
|
||||
|
||||
|
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 Wochentage
|
||||
中国大陆节假日与调休 Feiertage und Ausgleichsarbeitstage in Festlandchina
|
||||
传统与公历节日 Traditionelle und gregorianische Feste
|
||||
二十四节气 24 Sonnenperioden
|
||||
节气与时令 Sonnenperioden und Jahreszeiten
|
||||
三伏天 Hundstage
|
||||
梅雨天 Traditionelle Pflaumenregenzeit
|
||||
三伏·梅雨·数九 Fu · Meiyu · Neuner
|
||||
初伏 Fu 1
|
||||
中伏 Fu 2
|
||||
末伏 Fu 3
|
||||
|
||||
|
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 Días de la semana
|
||||
中国大陆节假日与调休 Festivos y días laborales compensatorios de China continental
|
||||
传统与公历节日 Fiestas tradicionales y gregorianas
|
||||
二十四节气 24 términos solares
|
||||
节气与时令 Términos solares y períodos estacionales
|
||||
三伏天 Días caniculares
|
||||
梅雨天 Lluvias de ciruela tradicionales
|
||||
三伏·梅雨·数九 Fu · Meiyu · Nueves
|
||||
初伏 Fu inicial
|
||||
中伏 Fu medio
|
||||
末伏 Fu final
|
||||
|
||||
|
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 Jours de la semaine
|
||||
中国大陆节假日与调休 Jours fériés et jours de travail compensatoires en Chine continentale
|
||||
传统与公历节日 Fêtes traditionnelles et grégoriennes
|
||||
二十四节气 24 périodes solaires
|
||||
节气与时令 Termes solaires et périodes saisonnières
|
||||
三伏天 Jours caniculaires
|
||||
梅雨天 Pluies des prunes traditionnelles
|
||||
三伏·梅雨·数九 Fu · Meiyu · Neuvaines
|
||||
初伏 Fu initial
|
||||
中伏 Fu médian
|
||||
末伏 Fu final
|
||||
|
||||
|
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 曜日
|
||||
中国大陆节假日与调休 中国本土の祝日・振替出勤日
|
||||
传统与公历节日 伝統と西暦の祝祭日
|
||||
二十四节气 二十四節気
|
||||
节气与时令 節気と季節の節目
|
||||
三伏天 三伏
|
||||
梅雨天 入梅・出梅
|
||||
三伏·梅雨·数九 三伏・梅雨・数九
|
||||
初伏 初伏
|
||||
中伏 中伏
|
||||
末伏 末伏
|
||||
|
||||
|
@@ -1316,10 +1316,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 요일
|
||||
中国大陆节假日与调休 중국 본토 공휴일 및 대체 근무일
|
||||
传统与公历节日 전통 및 양력 기념일
|
||||
二十四节气 24절기
|
||||
节气与时令 절기와 계절
|
||||
三伏天 삼복
|
||||
梅雨天 입매·출매
|
||||
三伏·梅雨·数九 삼복·매우·구구
|
||||
初伏 초복
|
||||
中伏 중복
|
||||
末伏 말복
|
||||
|
||||
|
@@ -1317,10 +1317,9 @@ Apple 智能 Apple Intelligence
|
||||
星期 星期
|
||||
中国大陆节假日与调休 中國大陸節假日與調休
|
||||
传统与公历节日 傳統與公曆節日
|
||||
二十四节气 二十四節氣
|
||||
节气与时令 節氣與時令
|
||||
三伏天 三伏天
|
||||
梅雨天 梅雨天
|
||||
三伏·梅雨·数九 三伏·梅雨·數九
|
||||
初伏 初伏
|
||||
中伏 中伏
|
||||
末伏 末伏
|
||||
|
||||
|
@@ -491,7 +491,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
calendarFirstWeekday: settings.calendarFirstWeekday,
|
||||
showsLunar: settings.calendarFeatures.contains(.lunar),
|
||||
calendarDays: calendarDays, monthSummaries: monthSummaries,
|
||||
showsSeasonal: settings.calendarFeatures.contains(.seasonal),
|
||||
showsSeasonal: settings.calendarFeatures.contains(.seasonalInfo),
|
||||
calendarDataVersion: dataVersion, showsAlmanac: settings.calendarPreferences.showAlmanac)
|
||||
guard snapshot != previous, widgetStore.save(snapshot) else { return }
|
||||
for kind in snapshot.changedWidgetKinds(from: previous) {
|
||||
|
||||
@@ -69,9 +69,9 @@ extension CalendarEngine {
|
||||
schedule = day.isWeekend ? .weekend : .work
|
||||
}
|
||||
let almanac = showsAlmanac ? almanac(for: day) : nil
|
||||
let seasons = features.contains(.seasonal) ? [day.dogDays, day.plumRain, day.nineDays].compactMap { $0 } : []
|
||||
let seasons = features.contains(.seasonalInfo) ? [day.dogDays, day.plumRain, day.nineDays].compactMap { $0 } : []
|
||||
return .init(dateKey: day.id, festivals: features.contains(.festivals) ? day.festivals : [],
|
||||
solarTerm: features.contains(.solarTerms) ? day.solarTerm : nil,
|
||||
solarTerm: features.contains(.seasonalInfo) ? day.solarTerm : nil,
|
||||
schedule: schedule, holidayName: features.contains(.holidays) ? day.holiday?.name : nil,
|
||||
twelveStar: almanac?.twelveStar, isEcliptic: almanac?.twelveStarIsLucky ?? false,
|
||||
recommends: Array(almanac?.recommends.prefix(3) ?? []),
|
||||
|
||||
@@ -193,7 +193,7 @@ struct CalendarAlmanacView: View {
|
||||
Text(row.value).frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
if features.contains(.seasonal), day.plumRain != nil {
|
||||
if features.contains(.seasonalInfo), day.plumRain != nil {
|
||||
Text(tr("按传统历法推算,并非天气预报")).font(.caption2).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
@@ -211,10 +211,11 @@ struct CalendarAlmanacView: View {
|
||||
if features.contains(.festivals), !day.festivals.isEmpty {
|
||||
rows.append(.init(feature: .festivals, value: day.festivals.map { tr($0) }.joined(separator: " · ")))
|
||||
}
|
||||
if features.contains(.solarTerms), let value = day.solarTerm { rows.append(.init(feature: .solarTerms, value: tr(value))) }
|
||||
if features.contains(.seasonal) {
|
||||
let seasons = [day.dogDays, day.plumRain, day.nineDays].compactMap { $0 }
|
||||
if !seasons.isEmpty { rows.append(.init(feature: .seasonal, value: seasons.map(tr).joined(separator: " · "))) }
|
||||
if features.contains(.seasonalInfo) {
|
||||
let values = [day.solarTerm, day.dogDays, day.plumRain, day.nineDays].compactMap { $0 }
|
||||
if !values.isEmpty {
|
||||
rows.append(.init(feature: .seasonalInfo, value: values.map(tr).joined(separator: " · ")))
|
||||
}
|
||||
}
|
||||
return rows + CalendarEngine.additionalCalendars(for: day, features: features)
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ import WidgetData
|
||||
|
||||
/// 公历是月历的坐标;其余信息可独立隐藏,设置以稳定 rawValue 保存和同步。
|
||||
public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable {
|
||||
case lunar, weekdays, holidays, festivals, solarTerms, seasonal, tibetan, hijri
|
||||
case lunar, weekdays, holidays, festivals, seasonalInfo, tibetan, hijri
|
||||
public var id: String { rawValue }
|
||||
public static var defaults: Set<Self> { defaults(for: .current) }
|
||||
|
||||
/// 默认值依据使用地区,不依据应用语言或设备销售地;其他地区可自行启用。
|
||||
public static func defaults(for locale: Locale) -> Set<Self> {
|
||||
locale.region?.identifier == "CN" ? [.lunar, .weekdays, .holidays, .festivals, .solarTerms] : [.weekdays]
|
||||
locale.region?.identifier == "CN" ? [.lunar, .weekdays, .holidays, .festivals, .seasonalInfo] : [.weekdays]
|
||||
}
|
||||
|
||||
var title: String {
|
||||
@@ -24,18 +24,20 @@ public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable {
|
||||
case .weekdays: tr("星期")
|
||||
case .holidays: tr("中国大陆节假日与调休")
|
||||
case .festivals: tr("传统与公历节日")
|
||||
case .solarTerms: tr("二十四节气")
|
||||
case .seasonal: tr("三伏·梅雨·数九")
|
||||
case .seasonalInfo: tr("节气与时令")
|
||||
case .tibetan: tr("藏历")
|
||||
case .hijri: tr("回历")
|
||||
}
|
||||
}
|
||||
|
||||
/// 旧偏好和备份里的两个独立开关合并为一个;任一项曾开启就保留用户的显示意愿。
|
||||
/// 旧偏好与备份中的节气、时令开关合并;任一项曾开启就保留用户的显示意愿。
|
||||
static func restored(from rawValues: [String]) -> Set<Self> {
|
||||
// 已移除的月历干支(ganzhi)与未知选项一起忽略,不影响其他已保存的选择。
|
||||
var features = Set(rawValues.compactMap(Self.init(rawValue:)))
|
||||
if rawValues.contains("dogDays") || rawValues.contains("plumRain") { features.insert(.seasonal) }
|
||||
// 使用新的设置键也使 Widget 的旧摘要失效,避免复用只包含节气或时令的旧缓存。
|
||||
if rawValues.contains(where: { ["solarTerms", "seasonal", "dogDays", "plumRain"].contains($0) }) {
|
||||
features.insert(.seasonalInfo)
|
||||
}
|
||||
return features
|
||||
}
|
||||
}
|
||||
@@ -79,9 +81,9 @@ struct CalendarDay: Identifiable, Equatable {
|
||||
/// 每格仅显示一条主注释,所有启用的信息在选中日期详情中保留。
|
||||
func subtitle(features: Set<CalendarFeature>) -> String {
|
||||
// 一九与冬至同日,阶段起点优先展示;节日和节气仍可在单日详情查看。
|
||||
if features.contains(.seasonal), let seasonalBoundary { return seasonalBoundary }
|
||||
if features.contains(.seasonalInfo), let seasonalBoundary { return seasonalBoundary }
|
||||
if features.contains(.festivals), let festival = festivals.first { return festival }
|
||||
if features.contains(.solarTerms), let solarTerm { return solarTerm }
|
||||
if features.contains(.seasonalInfo), let solarTerm { return solarTerm }
|
||||
return features.contains(.lunar) ? lunarLabel : ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Testing
|
||||
#expect(table.translate("最近刷新:20:53") == "Last refresh: 20:53")
|
||||
#expect(table.translate("12 个进程") == "12 processes")
|
||||
#expect(table.translate("1 个进程") == "1 process")
|
||||
#expect(table.translate("三伏·梅雨·数九") == "Fu · Plum rain · Nines")
|
||||
#expect(table.translate("节气与时令") == "Solar terms and seasons")
|
||||
#expect(table.translate("一九第2天") == "Nine 1, day 2")
|
||||
#expect(table.translate("不存在的文案") == nil)
|
||||
}
|
||||
|
||||
@@ -147,12 +147,12 @@ struct CalendarEnhancementTests {
|
||||
defer { defaults.removePersistentDomain(forName: name) }
|
||||
let locale = Locale(identifier: localeID)
|
||||
let mainland = locale.region?.identifier == "CN"
|
||||
let expected: Set<CalendarFeature> = mainland ? [.lunar, .weekdays, .holidays, .festivals, .solarTerms] : [.weekdays]
|
||||
let expected: Set<CalendarFeature> = mainland ? [.lunar, .weekdays, .holidays, .festivals, .seasonalInfo] : [.weekdays]
|
||||
let settings = AppSettings(defaults: defaults, calendarLocale: locale)
|
||||
#expect(settings.calendarFeatures == expected)
|
||||
#expect(settings.calendarPreferences.showHolidayOverview == mainland)
|
||||
#expect(settings.calendarPreferences.showAlmanac == mainland)
|
||||
#expect(!settings.calendarFeatures.contains(.seasonal))
|
||||
#expect(settings.calendarFeatures.contains(.seasonalInfo) == mainland)
|
||||
|
||||
// 更改地区不应重置首次保存的默认值,明确关闭的选项和空集合也须保留。
|
||||
let otherRegion = Locale(identifier: mainland ? "zh_TW" : "en_CN")
|
||||
@@ -217,14 +217,14 @@ struct CalendarEnhancementTests {
|
||||
defaults.set(["lunar", "holidays", "dogDays"], forKey: "calendarFeatures")
|
||||
defaults.set(Data(#"{"display":"lunar","showHolidayOverview":true}"#.utf8), forKey: "calendarPreferences")
|
||||
let settings = AppSettings(defaults: defaults, calendarLocale: Locale(identifier: "en_US"))
|
||||
#expect(settings.calendarFeatures == [.lunar, .holidays, .seasonal])
|
||||
#expect(settings.calendarFeatures == [.lunar, .holidays, .seasonalInfo])
|
||||
#expect(settings.calendarPreferences.display == .lunar)
|
||||
#expect(settings.calendarPreferences.showHolidayOverview && settings.calendarPreferences.showAlmanac)
|
||||
let backup = try JSONDecoder().decode(SettingsDocument.self, from: JSONEncoder().encode(settings.exportDocument()))
|
||||
settings.calendarFeatures = [.weekdays]
|
||||
settings.calendarPreferences.showAlmanac = false
|
||||
settings.apply(backup)
|
||||
#expect(settings.calendarFeatures == [.lunar, .holidays, .seasonal])
|
||||
#expect(settings.calendarFeatures == [.lunar, .holidays, .seasonalInfo])
|
||||
#expect(settings.calendarPreferences.showAlmanac)
|
||||
}
|
||||
|
||||
|
||||
@@ -120,15 +120,15 @@ struct CalendarTests {
|
||||
]
|
||||
for (year, month, day, label) in examples {
|
||||
let date = try #require(CalendarEngine.day(year: year, month: month, day: day, timeZone: zone))
|
||||
#expect(date.subtitle(features: [.seasonal]) == label)
|
||||
#expect(date.subtitle(features: [.seasonal, .festivals, .solarTerms, .lunar]) == label)
|
||||
#expect(date.subtitle(features: [.seasonalInfo]) == label)
|
||||
#expect(date.subtitle(features: [.seasonalInfo, .festivals, .lunar]) == label)
|
||||
}
|
||||
for (year, month, day) in [(2012, 8, 8), (2024, 6, 12), (2020, 12, 22)] {
|
||||
let date = try #require(CalendarEngine.day(year: year, month: month, day: day, timeZone: zone))
|
||||
#expect(date.subtitle(features: [.seasonal]).isEmpty)
|
||||
#expect(date.subtitle(features: [.seasonalInfo]).isEmpty)
|
||||
}
|
||||
let june = CalendarEngine.widgetMonthSummary(year: 2024, month: 6, firstWeekday: 2,
|
||||
features: [.seasonal], timeZone: zone)
|
||||
features: [.seasonalInfo], timeZone: zone)
|
||||
#expect(june.days.first { $0.dateKey == "2024-06-11" }?.subtitle == "入梅")
|
||||
#expect(june.days.first { $0.dateKey == "2024-06-12" }?.subtitle == "")
|
||||
}
|
||||
@@ -227,18 +227,52 @@ struct CalendarTests {
|
||||
#expect(restored.calendarFirstWeekday == 1)
|
||||
}
|
||||
|
||||
@Test(arguments: [[], ["solarTerms"], ["seasonal"], ["solarTerms", "seasonal"], ["dogDays"], ["plumRain"], ["seasonalInfo"]])
|
||||
func mergedSeasonalSettingPreservesEitherLegacyChoice(legacy: [String]) throws {
|
||||
let name = "CalendarMergedSeasonalTests.\(UUID())"
|
||||
let defaults = try #require(UserDefaults(suiteName: name))
|
||||
defer { defaults.removePersistentDomain(forName: name) }
|
||||
defaults.set(legacy + ["weekdays"], forKey: "calendarFeatures")
|
||||
let settings = AppSettings(defaults: defaults)
|
||||
let expected = Set(legacy.isEmpty ? ["weekdays"] : ["weekdays", "seasonalInfo"])
|
||||
#expect(Set(settings.calendarFeatures.map(\.rawValue)) == expected)
|
||||
#expect(!CalendarFeature.allCases.map(\.rawValue).contains("solarTerms"))
|
||||
#expect(!CalendarFeature.allCases.map(\.rawValue).contains("seasonal"))
|
||||
settings.calendarFeatures = []
|
||||
var backup = SettingsDocument()
|
||||
backup.calendarFeatures = legacy + ["weekdays"]
|
||||
settings.apply(backup)
|
||||
#expect(Set(settings.exportDocument().calendarFeatures ?? []) == expected)
|
||||
}
|
||||
|
||||
@Test(arguments: ["solarTerms", "seasonal"])
|
||||
func mergedSeasonalChoiceControlsTermsAndSeasonalSummaries(legacy: String) throws {
|
||||
let enabled = CalendarFeature.restored(from: [legacy])
|
||||
let term = try #require(CalendarEngine.day(year: 2026, month: 9, day: 23, timeZone: zone))
|
||||
let season = try #require(CalendarEngine.day(year: 2024, month: 6, day: 11, timeZone: zone))
|
||||
#expect(CalendarEngine.widgetSummary(for: term, features: enabled).solarTerm == term.solarTerm)
|
||||
let seasonalDescription = try #require(season.plumRain)
|
||||
#expect(CalendarEngine.widgetSummary(for: season, features: enabled).seasonalDescriptions == [seasonalDescription])
|
||||
#expect(CalendarEngine.widgetSummary(for: term, features: []).solarTerm == nil)
|
||||
#expect(CalendarEngine.widgetSummary(for: season, features: []).seasonalDescriptions == nil)
|
||||
#expect(term.subtitle(features: enabled) == term.solarTerm)
|
||||
#expect(season.subtitle(features: enabled) == season.seasonalBoundary)
|
||||
let summary = CalendarEngine.widgetMonthSummary(year: 2026, month: 9, firstWeekday: 2, features: enabled, timeZone: zone)
|
||||
#expect(summary.featureKeys == ["seasonalInfo"])
|
||||
}
|
||||
|
||||
@Test func oldSeasonalTogglesRestoreAsOneSetting() throws {
|
||||
let name = "CalendarSeasonalTests.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: name))
|
||||
defer { defaults.removePersistentDomain(forName: name) }
|
||||
defaults.set(["dogDays", "plumRain", "lunar"], forKey: "calendarFeatures")
|
||||
let settings = AppSettings(defaults: defaults)
|
||||
#expect(settings.calendarFeatures == [.seasonal, .lunar])
|
||||
#expect(settings.calendarFeatures == [.seasonalInfo, .lunar])
|
||||
|
||||
var oldBackup = SettingsDocument()
|
||||
oldBackup.calendarFeatures = ["plumRain", "weekdays"]
|
||||
settings.apply(oldBackup)
|
||||
#expect(settings.calendarFeatures == [.seasonal, .weekdays])
|
||||
#expect(settings.exportDocument().calendarFeatures == ["seasonal", "weekdays"])
|
||||
#expect(settings.calendarFeatures == [.seasonalInfo, .weekdays])
|
||||
#expect(settings.exportDocument().calendarFeatures == ["seasonalInfo", "weekdays"])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user