Packages/MoonriseKit/Tests/SkyEngineTests/EclipseTimelineTests.swift
84 lines · 3781 bytes
10 symbols in this file
timelineoffCuesglassesOffOnlyAtSecondContactWithAPreciseFixglassesOnTenSecondsBeforeThirdContactnoGlassesOffWithoutAPreciseFixnoGlassesOffForShortTotalitypartialSiteKeepsGlassesOnThroughoutscriptHasShadowBandAndBeadCuesInOrderrehearsalStartsThreeMinutesBeforeTotalityOrMaximumsunAtEachContact
1import Foundation
2import Testing
3@testable import SkyEngine
4
5private let peak2027 = Date(timeIntervalSince1970: 1_817_201_220)
6private let tarifa = Observer(latitude: 36.0143, longitude: -5.6044)
7
8private func timeline(_ observer: Observer, accuracy: Double?) throws -> EclipseTimeline {
9 try EclipseTimeline(try #require(LocalEclipse.at(observer, near: peak2027)), observer: observer, accuracy: accuracy)
10}
11
12private func offCues(_ t: EclipseTimeline) -> [EclipseTimeline.Cue] { t.cues.filter { $0.text.localizedCaseInsensitiveContains("glasses off") } }
13
14@Test func glassesOffOnlyAtSecondContactWithAPreciseFix() throws {
15 let t = try timeline(tarifa, accuracy: 5)
16 #expect(t.glassesOffWithheld == nil)
17 let off = offCues(t)
18 #expect(off.count == 1)
19 #expect(off.first?.date == t.eclipse.centralBegin)
20 #expect(off.first?.text.contains("last bead") == true)
21}
22
23@Test func glassesOnTenSecondsBeforeThirdContact() throws {
24 for accuracy in [5.0, 500, nil] {
25 let t = try timeline(tarifa, accuracy: accuracy)
26 let c3 = try #require(t.eclipse.centralEnd)
27 let on = t.cues.filter { $0.text.hasPrefix("Glasses on") }
28 #expect(on.map(\.date) == [c3.addingTimeInterval(-10)])
29 #expect(on.first?.warning == true)
30 }
31}
32
33@Test func noGlassesOffWithoutAPreciseFix() throws {
34 let coarse = try timeline(tarifa, accuracy: 100)
35 #expect(offCues(coarse).isEmpty)
36 #expect(coarse.glassesOffWithheld == .imprecise)
37 let city = try timeline(tarifa, accuracy: nil)
38 #expect(offCues(city).isEmpty)
39 #expect(city.glassesOffWithheld == .imprecise)
40}
41
42@Test func noGlassesOffForShortTotality() throws {
43 var e = try #require(LocalEclipse.at(tarifa, near: peak2027))
44 e.centralEnd = e.centralBegin?.addingTimeInterval(29.9)
45 let t = try EclipseTimeline(e, observer: tarifa, accuracy: 5)
46 #expect(offCues(t).isEmpty)
47 #expect(t.glassesOffWithheld == .short)
48}
49
50@Test func partialSiteKeepsGlassesOnThroughout() throws {
51 let seville = Observer(latitude: 37.3891, longitude: -5.9845) // 98% covered, no totality
52 let t = try timeline(seville, accuracy: 5)
53 #expect(t.eclipse.kind == "Partial")
54 #expect(offCues(t).isEmpty)
55 #expect(t.glassesOffWithheld == .partial)
56 #expect(t.cues.first?.text.contains("keep your glasses on the whole time") == true)
57}
58
59@Test func scriptHasShadowBandAndBeadCuesInOrder() throws {
60 let t = try timeline(tarifa, accuracy: 5)
61 let c2 = try #require(t.eclipse.centralBegin)
62 #expect(t.cues.contains { $0.date == c2.addingTimeInterval(-120) && $0.text.contains("shadow bands") })
63 #expect(t.cues.contains { $0.date == c2.addingTimeInterval(-15) && $0.text.contains("Baily's beads") })
64 #expect(t.cues.map(\.date) == t.cues.map(\.date).sorted())
65 #expect(t.cues.first?.date == t.eclipse.partialBegin && t.cues.last?.date == t.eclipse.partialEnd)
66}
67
68@Test func rehearsalStartsThreeMinutesBeforeTotalityOrMaximum() throws {
69 let t = try timeline(tarifa, accuracy: 5)
70 #expect(t.rehearsalStart == t.eclipse.centralBegin?.addingTimeInterval(-180))
71 let madrid = try timeline(Observer(latitude: 40.42, longitude: -3.70), accuracy: 5)
72 #expect(madrid.rehearsalStart == madrid.eclipse.peak.addingTimeInterval(-180))
73}
74
75@Test func sunAtEachContact() throws {
76 let t = try timeline(tarifa, accuracy: 5)
77 #expect(t.contacts.map(\.name) == ["C1", "C2", "Max", "C3", "C4"])
78 let c2 = t.contacts[1].sun
79 // Tarifa at totality: hour angle −56°, declination +17.8°, so about 38° up and just south of east.
80 #expect(abs(c2.altitude - 38) < 2)
81 #expect(c2.azimuth > 92 && c2.azimuth < 98)
82 let path = try t.sunPath(every: 300)
83 #expect(path.first?.date == t.eclipse.partialBegin && path.last?.date == t.eclipse.partialEnd)
84}