package svg import ( "errors" "fmt" "math" "strings" "testing" "time" "b612.me/astro/moon" ) func TestFindLocalStarOccultationSVGsHR4799(t *testing.T) { location := time.FixedZone("UTC+8", 8*3600) diagrams, err := FindLocalStarOccultationSVGs( time.Date(2025, 6, 5, 0, 0, 0, 0, location), time.Date(2025, 6, 6, 0, 0, 0, 0, location), hr4799StarCoordinate(), 121.56601, 6.80706, 0, moon.OccultationSearchOptions{}, LocalStarOccultationSVGOptions{Width: 720, Height: 560, Location: location}, ) if err != nil { t.Fatalf("FindLocalStarOccultationSVGs() error = %v", err) } if len(diagrams) != 1 { t.Fatalf("FindLocalStarOccultationSVGs() returned %d diagrams, want 1", len(diagrams)) } diagram := diagrams[0] for _, want := range []string{ ` 1e-6 && appearance.illumination < 1-1e-6 && !strings.Contains(diagram, ``) { t.Fatal("non-full Moon overview is missing its phase clip path") } } func TestLocalOccultationMoonPhasePathSelectsIlluminatedArea(t *testing.T) { gibbous := localOccultationMoonPhasePath(100, 0.75) if !strings.Contains(gibbous, `A 50.000 100.000 0 0 1 0 -100.000`) { t.Fatalf("waxing or waning gibbous terminator uses the wrong arc: %s", gibbous) } crescent := localOccultationMoonPhasePath(100, 0.25) if !strings.Contains(crescent, `A 50.000 100.000 0 0 0 0 -100.000`) { t.Fatalf("crescent terminator uses the wrong arc: %s", crescent) } } func TestLocalOccultationMoonSVGRotationUsesNorthUpEastLeft(t *testing.T) { tests := []struct { positionAngle float64 rotation float64 }{ {positionAngle: 0, rotation: -90}, {positionAngle: 90, rotation: -180}, {positionAngle: 180, rotation: -270}, {positionAngle: 270, rotation: -360}, } for _, test := range tests { if got := localOccultationMoonSVGRotation(test.positionAngle); got != test.rotation { t.Fatalf("bright-limb PA %.1f rotation = %.1f, want %.1f", test.positionAngle, got, test.rotation) } } } func TestLocalStarOccultationSVGEnglishAndCustomText(t *testing.T) { info := localHR4799Occultation(t) diagram, err := LocalStarOccultationSVG(info, hr4799StarCoordinate(), LocalStarOccultationSVGOptions{ Language: "en", Location: time.UTC, Title: "Custom local title", SummaryText: "Custom local summary", GreatestText: "Custom local greatest", OverviewTitle: "Custom local overview", PhasePanelsTitle: "Custom local stages", ContactsTitle: "Custom local contacts", DirectionText: "Custom local direction", FooterNote: "Custom local footer", }) if err != nil { t.Fatalf("LocalStarOccultationSVG() error = %v", err) } for _, want := range []string{ "Custom local title", "Custom local summary", "Custom local greatest", "Custom local overview", "Custom local stages", "Custom local contacts", "Custom local direction", "Custom local footer", "Immersion", "Greatest", "Emersion", "Lunar path", } { if !strings.Contains(diagram, want) { t.Fatalf("English local SVG missing %q", want) } } } func TestLocalStarOccultationSVGLunarPathUsesGreatestTangent(t *testing.T) { frames := []moon.StarOccultationDiagramFrame{ {StarXArcsec: 100, StarYArcsec: 100}, {StarXArcsec: -1, StarYArcsec: -2}, {StarXArcsec: 0, StarYArcsec: 0, Label: "Greatest", Labels: []string{"Greatest"}}, {StarXArcsec: 1, StarYArcsec: 2}, {StarXArcsec: -100, StarYArcsec: 100}, } unitX, unitY, ok := localStarOccultationSVGLunarPathDirection(frames) if !ok { t.Fatalf("localStarOccultationSVGLunarPathDirection() returned no direction") } if math.Abs(unitX-1/math.Sqrt(5)) > 1e-12 || math.Abs(unitY-2/math.Sqrt(5)) > 1e-12 { t.Fatalf("lunar path direction = %.12f %.12f, want greatest tangent", unitX, unitY) } } func TestFindLocalStarOccultationSVGsNoEvent(t *testing.T) { location := time.FixedZone("CST", 8*3600) star := hr4799StarCoordinate() star.Dec = 80 diagrams, err := FindLocalStarOccultationSVGs( time.Date(2025, 6, 5, 0, 0, 0, 0, location), time.Date(2025, 6, 6, 0, 0, 0, 0, location), star, 121.56601, 6.80706, 0, moon.OccultationSearchOptions{}, LocalStarOccultationSVGOptions{}, ) if err != nil { t.Fatalf("FindLocalStarOccultationSVGs() error = %v", err) } if len(diagrams) != 0 { t.Fatalf("FindLocalStarOccultationSVGs() returned %d diagrams, want none", len(diagrams)) } } func TestLocalStarOccultationSVGRejectsInvalidInput(t *testing.T) { info := localHR4799Occultation(t) _, err := LocalStarOccultationSVG(info, hr4799StarCoordinate(), LocalStarOccultationSVGOptions{Width: 1}) if !errors.Is(err, ErrInvalidLocalStarOccultationSVGOptions) { t.Fatalf("invalid canvas error = %v, want ErrInvalidLocalStarOccultationSVGOptions", err) } info.ContactsComplete = false _, err = LocalStarOccultationSVG(info, hr4799StarCoordinate(), LocalStarOccultationSVGOptions{}) if !errors.Is(err, ErrInvalidLocalStarOccultationInfo) { t.Fatalf("incomplete event error = %v, want ErrInvalidLocalStarOccultationInfo", err) } info = localHR4799Occultation(t) otherStar := hr4799StarCoordinate() otherStar.ID = "another-star" _, err = LocalStarOccultationSVG(info, otherStar, LocalStarOccultationSVGOptions{}) if !errors.Is(err, ErrInvalidLocalStarOccultationInfo) { t.Fatalf("mismatched target error = %v, want ErrInvalidLocalStarOccultationInfo", err) } info = localHR4799Occultation(t) wrongCoordinate := hr4799StarCoordinate() wrongCoordinate.RA += 30 _, err = LocalStarOccultationSVG(info, wrongCoordinate, LocalStarOccultationSVGOptions{}) if !errors.Is(err, ErrInvalidLocalStarOccultationInfo) { t.Fatalf("same-ID wrong coordinate error = %v, want ErrInvalidLocalStarOccultationInfo", err) } info.TargetID = "" wrongCoordinate.ID = "" _, err = LocalStarOccultationSVG(info, wrongCoordinate, LocalStarOccultationSVGOptions{}) if !errors.Is(err, ErrInvalidLocalStarOccultationInfo) { t.Fatalf("empty-ID wrong coordinate error = %v, want ErrInvalidLocalStarOccultationInfo", err) } } func localHR4799Occultation(t *testing.T) moon.StarOccultationInfo { t.Helper() location := time.FixedZone("CST", 8*3600) events, err := moon.FindStarOccultations( time.Date(2025, 6, 5, 0, 0, 0, 0, location), time.Date(2025, 6, 6, 0, 0, 0, 0, location), hr4799StarCoordinate(), 121.56601, 6.80706, 0, moon.OccultationSearchOptions{}, ) if err != nil { t.Fatalf("FindStarOccultations() error = %v", err) } if len(events) != 1 { t.Fatalf("FindStarOccultations() returned %d events, want 1", len(events)) } return events[0] }